Support for jsonpath .datetime() method

Started by Alexander Korotkovover 6 years ago22 messages
#1Alexander Korotkov
a.korotkov@postgrespro.ru
4 attachment(s)

Hi!

Attached patchset implements jsonpath .datetime() method.

* 0001-datetime-in-JsonbValue-1.patch
This patch allows JsonbValue struct to hold datetime values. It
appears to be convenient since jsonpath execution engine uses
JsonbValue to store intermediate calculation results. On
serialization datetime values are converted into strings.

* 0002-datetime-conversion-for-jsonpath-1.patch
This patch adds some datetime conversion infrastructure missing
according to SQL/JSON standard. It includes FF1-FF6 format patterns,
runtime identification of datetime type, strict parsing mode.

* 0003-error-suppression-for-datetime-1.path
As jsonpath supports error suppression in general, it's required for
datetime functions too. This commit implements it in the same manner
as we did for numerics before.

* 0004-implement-jsonpath-datetime-1.path
.datetime() method itself and additionally comparison of datetime
values. Here goes a trick. Out exising jsonb_path_*() functions are
immutable, while comparison of timezoned and non-timezoned type is
obviously not. This patch makes existing immutable jsonb_path_*()
functions throw error on non-immutable comparison. Additionally it
implements stable jsonb_path_*_tz() functions, which support full set
of features.

I was going to discuss this patchset among the other SQL/JSON problems
on PGCon unconference, but I didn't make it there. I found most
questionable point in this patchset to be two sets of functions:
immutable and stable. However, I don't see better solution here: we
need immutable functions for expression indexes, and also we need
function with full set of jsonpath features, which are not all
immutable.

Sometimes immutability of jsonpath expression could be determined
runtime. When .datetime() method is used with template string
argument we may know result type in advance. Thus, in some times we
may know in advance that given jsonpath is immutable. So, we could
hack contain_mutable_functions_checker() or something to make an
exclusive heuristics for jsonb_path_*() functions. But I think it's
better to go with jsonb_path_*() and jsonb_path_*_tz() variants for
now. We could come back to idea of heuristics during consideration of
standard SQL/JSON clauses.

Any thoughts?

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0001-datetime-in-JsonbValue-1.patchapplication/octet-stream; name=0001-datetime-in-JsonbValue-1.patchDownload
commit 6d5b993bfbc072ee60d55435873a72634f6e3622
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Thu May 16 21:48:26 2019 +0300

    Allow datetime values in JsonbValue
    
    SQL/JSON standard allows manipulation with datetime values.  So, it appears to
    be convinient to allow datetime values to be represented in JsonbValue struct.
    These datetime values are allowed for temporary representation only.  During
    serialization datetime values are converted into strings.
    
    SQL/JSON requires writing timestamps with timezone in the same timezone offset
    as they were parsed.  This is why we allow storage of timezone offset in
    JsonbValue struct.  For the same reason timezone offset argument is added to
    JsonEncodeDateTime() function.

diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index 26d293709aa..7440f77cbe5 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -1506,7 +1506,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 			{
 				char		buf[MAXDATELEN + 1];
 
-				JsonEncodeDateTime(buf, val, DATEOID);
+				JsonEncodeDateTime(buf, val, DATEOID, NULL);
 				appendStringInfo(result, "\"%s\"", buf);
 			}
 			break;
@@ -1514,7 +1514,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 			{
 				char		buf[MAXDATELEN + 1];
 
-				JsonEncodeDateTime(buf, val, TIMESTAMPOID);
+				JsonEncodeDateTime(buf, val, TIMESTAMPOID, NULL);
 				appendStringInfo(result, "\"%s\"", buf);
 			}
 			break;
@@ -1522,7 +1522,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 			{
 				char		buf[MAXDATELEN + 1];
 
-				JsonEncodeDateTime(buf, val, TIMESTAMPTZOID);
+				JsonEncodeDateTime(buf, val, TIMESTAMPTZOID, NULL);
 				appendStringInfo(result, "\"%s\"", buf);
 			}
 			break;
@@ -1550,10 +1550,11 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 
 /*
  * Encode 'value' of datetime type 'typid' into JSON string in ISO format using
- * optionally preallocated buffer 'buf'.
+ * optionally preallocated buffer 'buf'.  Optional 'tzp' determines time-zone
+ * offset (in seconds) in which we want to show timestamptz.
  */
 char *
-JsonEncodeDateTime(char *buf, Datum value, Oid typid)
+JsonEncodeDateTime(char *buf, Datum value, Oid typid, const int *tzp)
 {
 	if (!buf)
 		buf = palloc(MAXDATELEN + 1);
@@ -1630,11 +1631,30 @@ JsonEncodeDateTime(char *buf, Datum value, Oid typid)
 				const char *tzn = NULL;
 
 				timestamp = DatumGetTimestampTz(value);
+
+				/*
+				 * If time-zone is specified, we apply a time-zone shift,
+				 * convert timestamptz to pg_tm as if it was without
+				 * time-zone, and then use specified time-zone for encoding
+				 * timestamp into a string.
+				 */
+				if (tzp)
+				{
+					tz = *tzp;
+					timestamp -= (TimestampTz) tz * USECS_PER_SEC;
+				}
+
 				/* Same as timestamptz_out(), but forcing DateStyle */
 				if (TIMESTAMP_NOT_FINITE(timestamp))
 					EncodeSpecialTimestamp(timestamp, buf);
-				else if (timestamp2tm(timestamp, &tz, &tm, &fsec, &tzn, NULL) == 0)
+				else if (timestamp2tm(timestamp, tzp ? NULL : &tz, &tm, &fsec,
+									  tzp ? NULL : &tzn, NULL) == 0)
+				{
+					if (tzp)
+						tm.tm_isdst = 1;	/* set time-zone presence flag */
+
 					EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf);
+				}
 				else
 					ereport(ERROR,
 							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 69f41ab4556..74b4bbe44c6 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -206,6 +206,24 @@ JsonbTypeName(JsonbValue *jbv)
 			return "boolean";
 		case jbvNull:
 			return "null";
+		case jbvDatetime:
+			switch (jbv->val.datetime.typid)
+			{
+				case DATEOID:
+					return "date";
+				case TIMEOID:
+					return "time without time zone";
+				case TIMETZOID:
+					return "time with time zone";
+				case TIMESTAMPOID:
+					return "timestamp without time zone";
+				case TIMESTAMPTZOID:
+					return "timestamp with time zone";
+				default:
+					elog(ERROR, "unrecognized jsonb value datetime type: %d",
+						 jbv->val.datetime.typid);
+			}
+			return "unknown";
 		default:
 			elog(ERROR, "unrecognized jsonb value type: %d", jbv->type);
 			return "unknown";
@@ -805,17 +823,20 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result,
 				break;
 			case JSONBTYPE_DATE:
 				jb.type = jbvString;
-				jb.val.string.val = JsonEncodeDateTime(NULL, val, DATEOID);
+				jb.val.string.val = JsonEncodeDateTime(NULL, val,
+													   DATEOID, NULL);
 				jb.val.string.len = strlen(jb.val.string.val);
 				break;
 			case JSONBTYPE_TIMESTAMP:
 				jb.type = jbvString;
-				jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPOID);
+				jb.val.string.val = JsonEncodeDateTime(NULL, val,
+													   TIMESTAMPOID, NULL);
 				jb.val.string.len = strlen(jb.val.string.val);
 				break;
 			case JSONBTYPE_TIMESTAMPTZ:
 				jb.type = jbvString;
-				jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPTZOID);
+				jb.val.string.val = JsonEncodeDateTime(NULL, val,
+													   TIMESTAMPTZOID, NULL);
 				jb.val.string.len = strlen(jb.val.string.val);
 				break;
 			case JSONBTYPE_JSONCAST:
diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index 7969f6f5843..cb2bd872cf5 100644
--- a/src/backend/utils/adt/jsonb_util.c
+++ b/src/backend/utils/adt/jsonb_util.c
@@ -14,9 +14,12 @@
 #include "postgres.h"
 
 #include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
 #include "miscadmin.h"
 #include "utils/builtins.h"
+#include "utils/datetime.h"
 #include "utils/hashutils.h"
+#include "utils/jsonapi.h"
 #include "utils/jsonb.h"
 #include "utils/memutils.h"
 #include "utils/varlena.h"
@@ -241,6 +244,7 @@ compareJsonbContainers(JsonbContainer *a, JsonbContainer *b)
 							res = (va.val.object.nPairs > vb.val.object.nPairs) ? 1 : -1;
 						break;
 					case jbvBinary:
+					case jbvDatetime:
 						elog(ERROR, "unexpected jbvBinary value");
 				}
 			}
@@ -1749,6 +1753,22 @@ convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal)
 				JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE;
 			break;
 
+		case jbvDatetime:
+			{
+				char		buf[MAXDATELEN + 1];
+				size_t		len;
+
+				JsonEncodeDateTime(buf,
+								   scalarVal->val.datetime.value,
+								   scalarVal->val.datetime.typid,
+								   &scalarVal->val.datetime.tz);
+				len = strlen(buf);
+				appendToBuffer(buffer, buf, len);
+
+				*jentry = JENTRY_ISSTRING | len;
+			}
+			break;
+
 		default:
 			elog(ERROR, "invalid jsonb scalar type");
 	}
diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h
index 35830df8c31..040c5914a45 100644
--- a/src/include/utils/jsonapi.h
+++ b/src/include/utils/jsonapi.h
@@ -161,6 +161,7 @@ extern Jsonb *transform_jsonb_string_values(Jsonb *jsonb, void *action_state,
 extern text *transform_json_string_values(text *json, void *action_state,
 										  JsonTransformStringValuesAction transform_action);
 
-extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid);
+extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid,
+								const int *tzp);
 
 #endif							/* JSONAPI_H */
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index 2fe7d32fec2..ec66d150263 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -241,7 +241,15 @@ enum jbvType
 	jbvArray = 0x10,
 	jbvObject,
 	/* Binary (i.e. struct Jsonb) jbvArray/jbvObject */
-	jbvBinary
+	jbvBinary,
+
+	/*
+	 * Virtual types.
+	 *
+	 * These types are used only for in-memory JSON processing and serialized
+	 * into JSON strings when outputted to json/jsonb.
+	 */
+	jbvDatetime = 0x20,
 };
 
 /*
@@ -282,11 +290,21 @@ struct JsonbValue
 			int			len;
 			JsonbContainer *data;
 		}			binary;		/* Array or object, in on-disk format */
+
+		struct
+		{
+			Datum		value;
+			Oid			typid;
+			int32		typmod;
+			int			tz;		/* Numeric time zone, in seconds for
+								 * TimestampTz data type */
+		}			datetime;
 	}			val;
 };
 
-#define IsAJsonbScalar(jsonbval)	((jsonbval)->type >= jbvNull && \
-									 (jsonbval)->type <= jbvBool)
+#define IsAJsonbScalar(jsonbval)	(((jsonbval)->type >= jbvNull && \
+									  (jsonbval)->type <= jbvBool) || \
+									  (jsonbval)->type == jbvDatetime)
 
 /*
  * Key/value pair within an Object.
0002-datetime-conversion-for-jsonpath-1.patchapplication/octet-stream; name=0002-datetime-conversion-for-jsonpath-1.patchDownload
commit fe28a5e1ea539b364c81bec385f3cd45ec97d566
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Wed Jan 23 05:55:49 2019 +0300

    Improve datetime conversion for upcoming jsonpath .datetime() method
    
    Jsonpath language (part of SQL/JSON standard) includes functions for datetime
    conversion.  In order to support that, we have to extend our infrastructure
    in following ways.
    
      1. FF1-FF6 format patterns implementing different fractions of second.  FF3
         and FF6 are effectively just synonyms for MS and US.  But other fractions
         were not implemented yet.
      2. to_datetime() internal function, which dynamically determines result
         datatype depending on format string.
      3. Strict parsing mode, which doesn't allow trailing spaces and unmatched
         format patterns.
    
    The first improvement is already user-visible and can use used in datetime
    parsing/printing functions.  Other improvements are internal, they will be
    user-visible together with jsonpath.
    
    Code was written by Nikita Glukhov and Teodor Sigaev, revised by me.
    Documentation was written by Oleg Bartunov and Liudmila Mantrova.  The work
    was inspired by Oleg Bartunov.
    
    Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
    Author: Nikita Glukhov, Teodor Sigaev, Alexander Korotkov, Oleg Bartunov, Liudmila Mantrova
    Reviewed-by: Andrew Dunstan, Pavel Stehule, Tomas Vondra, Alexander Korotkov

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index a79e7c0380b..2a62a7995c0 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -6146,6 +6146,30 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
         <entry><literal>US</literal></entry>
         <entry>microsecond (000000-999999)</entry>
        </row>
+       <row>
+        <entry><literal>FF1</literal></entry>
+        <entry>decisecond (0-9)</entry>
+       </row>
+       <row>
+        <entry><literal>FF2</literal></entry>
+        <entry>centisecond (00-99)</entry>
+       </row>
+       <row>
+        <entry><literal>FF3</literal></entry>
+        <entry>millisecond (000-999)</entry>
+       </row>
+       <row>
+        <entry><literal>FF4</literal></entry>
+        <entry>tenth of a millisecond (0000-9999)</entry>
+       </row>
+       <row>
+        <entry><literal>FF5</literal></entry>
+        <entry>hundredth of a millisecond (00000-99999)</entry>
+       </row>
+       <row>
+        <entry><literal>FF6</literal></entry>
+        <entry>microsecond (000000-999999)</entry>
+       </row>
        <row>
         <entry><literal>SSSS</literal></entry>
         <entry>seconds past midnight (0-86399)</entry>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 1ff3cfea8b9..4005f6b26db 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -41,11 +41,6 @@
 #endif
 
 
-static int	tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
-static int	tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
-static void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
-
-
 /* common code for timetypmodin and timetztypmodin */
 static int32
 anytime_typmodin(bool istz, ArrayType *ta)
@@ -1211,7 +1206,7 @@ time_in(PG_FUNCTION_ARGS)
 /* tm2time()
  * Convert a tm structure to a time data type.
  */
-static int
+int
 tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result)
 {
 	*result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec)
@@ -1387,7 +1382,7 @@ time_scale(PG_FUNCTION_ARGS)
  * have a fundamental tie together but rather a coincidence of
  * implementation. - thomas
  */
-static void
+void
 AdjustTimeForTypmod(TimeADT *time, int32 typmod)
 {
 	static const int64 TimeScales[MAX_TIME_PRECISION + 1] = {
@@ -1965,7 +1960,7 @@ time_part(PG_FUNCTION_ARGS)
 /* tm2timetz()
  * Convert a tm structure to a time data type.
  */
-static int
+int
 tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result)
 {
 	result->time = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) *
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 206576d4bd3..1e55cd4ea7b 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -86,6 +86,7 @@
 #endif
 
 #include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
 #include "mb/pg_wchar.h"
 #include "utils/builtins.h"
 #include "utils/date.h"
@@ -436,7 +437,8 @@ typedef struct
 				clock,			/* 12 or 24 hour clock? */
 				tzsign,			/* +1, -1 or 0 if timezone info is absent */
 				tzh,
-				tzm;
+				tzm,
+				ff;				/* fractional precision */
 } TmFromChar;
 
 #define ZERO_tmfc(_X) memset(_X, 0, sizeof(TmFromChar))
@@ -596,6 +598,12 @@ typedef enum
 	DCH_Day,
 	DCH_Dy,
 	DCH_D,
+	DCH_FF1,
+	DCH_FF2,
+	DCH_FF3,
+	DCH_FF4,
+	DCH_FF5,
+	DCH_FF6,
 	DCH_FX,						/* global suffix */
 	DCH_HH24,
 	DCH_HH12,
@@ -645,6 +653,12 @@ typedef enum
 	DCH_dd,
 	DCH_dy,
 	DCH_d,
+	DCH_ff1,
+	DCH_ff2,
+	DCH_ff3,
+	DCH_ff4,
+	DCH_ff5,
+	DCH_ff6,
 	DCH_fx,
 	DCH_hh24,
 	DCH_hh12,
@@ -745,7 +759,13 @@ static const KeyWord DCH_keywords[] = {
 	{"Day", 3, DCH_Day, false, FROM_CHAR_DATE_NONE},
 	{"Dy", 2, DCH_Dy, false, FROM_CHAR_DATE_NONE},
 	{"D", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
-	{"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},	/* F */
+	{"FF1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE},	/* F */
+	{"FF2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE},
+	{"FF3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE},
+	{"FF4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE},
+	{"FF5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE},
+	{"FF6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE},
+	{"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
 	{"HH24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE},	/* H */
 	{"HH12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
 	{"HH", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
@@ -794,7 +814,13 @@ static const KeyWord DCH_keywords[] = {
 	{"dd", 2, DCH_DD, true, FROM_CHAR_DATE_GREGORIAN},
 	{"dy", 2, DCH_dy, false, FROM_CHAR_DATE_NONE},
 	{"d", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
-	{"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},	/* f */
+	{"ff1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE},	/* f */
+	{"ff2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE},
+	{"ff3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE},
+	{"ff4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE},
+	{"ff5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE},
+	{"ff6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE},
+	{"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
 	{"hh24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE},	/* h */
 	{"hh12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
 	{"hh", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
@@ -895,10 +921,10 @@ static const int DCH_index[KeyWord_INDEX_SIZE] = {
 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 	-1, -1, -1, -1, -1, DCH_A_D, DCH_B_C, DCH_CC, DCH_DAY, -1,
-	DCH_FX, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
+	DCH_FF1, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
 	DCH_P_M, DCH_Q, DCH_RM, DCH_SSSS, DCH_TZH, DCH_US, -1, DCH_WW, -1, DCH_Y_YYY,
 	-1, -1, -1, -1, -1, -1, -1, DCH_a_d, DCH_b_c, DCH_cc,
-	DCH_day, -1, DCH_fx, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
+	DCH_day, -1, DCH_ff1, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
 	-1, -1, DCH_p_m, DCH_q, DCH_rm, DCH_ssss, DCH_tz, DCH_us, -1, DCH_ww,
 	-1, DCH_y_yyy, -1, -1, -1, -1
 
@@ -962,6 +988,10 @@ typedef struct NUMProc
 			   *L_currency_symbol;
 } NUMProc;
 
+/* Return flags for DCH_from_char() */
+#define DCH_DATED	0x01
+#define DCH_TIMED	0x02
+#define DCH_ZONED	0x04
 
 /* ----------
  * Functions
@@ -977,7 +1007,8 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 
 static void DCH_to_char(FormatNode *node, bool is_interval,
 						TmToChar *in, char *out, Oid collid);
-static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out);
+static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
+						  bool strict);
 
 #ifdef DEBUG_TO_FROM_CHAR
 static void dump_index(const KeyWord *k, const int *index);
@@ -994,8 +1025,8 @@ static int	from_char_parse_int_len(int *dest, char **src, const int len, FormatN
 static int	from_char_parse_int(int *dest, char **src, FormatNode *node);
 static int	seq_search(char *name, const char *const *array, int type, int max, int *len);
 static int	from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
-static void do_to_timestamp(text *date_txt, text *fmt,
-							struct pg_tm *tm, fsec_t *fsec);
+static void do_to_timestamp(text *date_txt, text *fmt, bool strict,
+							struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags);
 static char *fill_str(char *str, int c, int max);
 static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
 static char *int_to_roman(int number);
@@ -2517,18 +2548,32 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 					str_numth(s, s, S_TH_TYPE(n->suffix));
 				s += strlen(s);
 				break;
-			case DCH_MS:		/* millisecond */
-				sprintf(s, "%03d", (int) (in->fsec / INT64CONST(1000)));
-				if (S_THth(n->suffix))
-					str_numth(s, s, S_TH_TYPE(n->suffix));
+#define DCH_to_char_fsec(frac_fmt, frac_val) \
+				sprintf(s, frac_fmt, (int) (frac_val)); \
+				if (S_THth(n->suffix)) \
+					str_numth(s, s, S_TH_TYPE(n->suffix)); \
 				s += strlen(s);
+			case DCH_FF1:		/* decisecond */
+				DCH_to_char_fsec("%01d", in->fsec / 100000);
+				break;
+			case DCH_FF2:		/* centisecond */
+				DCH_to_char_fsec("%02d", in->fsec / 10000);
+				break;
+			case DCH_FF3:
+			case DCH_MS:		/* millisecond */
+				DCH_to_char_fsec("%03d", in->fsec / 1000);
+				break;
+			case DCH_FF4:
+				DCH_to_char_fsec("%04d", in->fsec / 100);
 				break;
+			case DCH_FF5:
+				DCH_to_char_fsec("%05d", in->fsec / 10);
+				break;
+			case DCH_FF6:
 			case DCH_US:		/* microsecond */
-				sprintf(s, "%06d", (int) in->fsec);
-				if (S_THth(n->suffix))
-					str_numth(s, s, S_TH_TYPE(n->suffix));
-				s += strlen(s);
+				DCH_to_char_fsec("%06d", in->fsec);
 				break;
+#undef DCH_to_char_fsec
 			case DCH_SSSS:
 				sprintf(s, "%d", tm->tm_hour * SECS_PER_HOUR +
 						tm->tm_min * SECS_PER_MINUTE +
@@ -3010,13 +3055,15 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 /* ----------
  * Process a string as denoted by a list of FormatNodes.
  * The TmFromChar struct pointed to by 'out' is populated with the results.
+ * 'strict' enables error reporting on unmatched trailing characters in input or
+ * format strings patterns.
  *
  * Note: we currently don't have any to_interval() function, so there
  * is no need here for INVALID_FOR_INTERVAL checks.
  * ----------
  */
 static void
-DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
+DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 {
 	FormatNode *n;
 	char	   *s;
@@ -3153,8 +3200,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 
 				SKIP_THth(s, n->suffix);
 				break;
+			case DCH_FF1:
+			case DCH_FF2:
+			case DCH_FF3:
+			case DCH_FF4:
+			case DCH_FF5:
+			case DCH_FF6:
+				out->ff = n->key->id - DCH_FF1 + 1;
+				/* fall through */
 			case DCH_US:		/* microsecond */
-				len = from_char_parse_int_len(&out->us, &s, 6, n);
+				len = from_char_parse_int_len(&out->us, &s,
+											  n->key->id == DCH_US ? 6 :
+											  out->ff, n);
 
 				out->us *= len == 1 ? 100000 :
 					len == 2 ? 10000 :
@@ -3380,6 +3437,23 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 			}
 		}
 	}
+
+	if (strict)
+	{
+		if (n->type != NODE_TYPE_END)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+					 errmsg("input string is too short for datetime format")));
+
+		while (*s != '\0' && isspace((unsigned char) *s))
+			s++;
+
+		if (*s != '\0')
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+					 errmsg("trailing characters remain in input string after "
+							"datetime format")));
+	}
 }
 
 /*
@@ -3400,6 +3474,109 @@ DCH_prevent_counter_overflow(void)
 	}
 }
 
+/* Get mask of date/time/zone components present in format nodes. */
+static int
+DCH_datetime_type(FormatNode *node)
+{
+	FormatNode *n;
+	int			flags = 0;
+
+	for (n = node; n->type != NODE_TYPE_END; n++)
+	{
+		if (n->type != NODE_TYPE_ACTION)
+			continue;
+
+		switch (n->key->id)
+		{
+			case DCH_FX:
+				break;
+			case DCH_A_M:
+			case DCH_P_M:
+			case DCH_a_m:
+			case DCH_p_m:
+			case DCH_AM:
+			case DCH_PM:
+			case DCH_am:
+			case DCH_pm:
+			case DCH_HH:
+			case DCH_HH12:
+			case DCH_HH24:
+			case DCH_MI:
+			case DCH_SS:
+			case DCH_MS:		/* millisecond */
+			case DCH_US:		/* microsecond */
+			case DCH_FF1:
+			case DCH_FF2:
+			case DCH_FF3:
+			case DCH_FF4:
+			case DCH_FF5:
+			case DCH_FF6:
+			case DCH_SSSS:
+				flags |= DCH_TIMED;
+				break;
+			case DCH_tz:
+			case DCH_TZ:
+			case DCH_OF:
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("formatting field \"%s\" is only supported in to_char",
+								n->key->name)));
+				flags |= DCH_ZONED;
+				break;
+			case DCH_TZH:
+			case DCH_TZM:
+				flags |= DCH_ZONED;
+				break;
+			case DCH_A_D:
+			case DCH_B_C:
+			case DCH_a_d:
+			case DCH_b_c:
+			case DCH_AD:
+			case DCH_BC:
+			case DCH_ad:
+			case DCH_bc:
+			case DCH_MONTH:
+			case DCH_Month:
+			case DCH_month:
+			case DCH_MON:
+			case DCH_Mon:
+			case DCH_mon:
+			case DCH_MM:
+			case DCH_DAY:
+			case DCH_Day:
+			case DCH_day:
+			case DCH_DY:
+			case DCH_Dy:
+			case DCH_dy:
+			case DCH_DDD:
+			case DCH_IDDD:
+			case DCH_DD:
+			case DCH_D:
+			case DCH_ID:
+			case DCH_WW:
+			case DCH_Q:
+			case DCH_CC:
+			case DCH_Y_YYY:
+			case DCH_YYYY:
+			case DCH_IYYY:
+			case DCH_YYY:
+			case DCH_IYY:
+			case DCH_YY:
+			case DCH_IY:
+			case DCH_Y:
+			case DCH_I:
+			case DCH_RM:
+			case DCH_rm:
+			case DCH_W:
+			case DCH_J:
+				flags |= DCH_DATED;
+				break;
+		}
+	}
+
+	return flags;
+}
+
 /* select a DCHCacheEntry to hold the given format picture */
 static DCHCacheEntry *
 DCH_cache_getnew(const char *str)
@@ -3688,8 +3865,9 @@ to_timestamp(PG_FUNCTION_ARGS)
 	int			tz;
 	struct pg_tm tm;
 	fsec_t		fsec;
+	int			fprec;
 
-	do_to_timestamp(date_txt, fmt, &tm, &fsec);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL);
 
 	/* Use the specified time zone, if any. */
 	if (tm.tm_zone)
@@ -3707,6 +3885,10 @@ to_timestamp(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 				 errmsg("timestamp out of range")));
 
+	/* Use the specified fractional precision, if any. */
+	if (fprec)
+		AdjustTimestampForTypmod(&result, fprec);
+
 	PG_RETURN_TIMESTAMP(result);
 }
 
@@ -3724,7 +3906,7 @@ to_date(PG_FUNCTION_ARGS)
 	struct pg_tm tm;
 	fsec_t		fsec;
 
-	do_to_timestamp(date_txt, fmt, &tm, &fsec);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL);
 
 	/* Prevent overflow in Julian-day routines */
 	if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3745,11 +3927,177 @@ to_date(PG_FUNCTION_ARGS)
 	PG_RETURN_DATEADT(result);
 }
 
+/*
+ * Make datetime type from 'date_txt' which is formated at argument 'fmt'.
+ * Actual datatype (returned in 'typid', 'typmod') is determined by
+ * presence of date/time/zone components in the format string.
+ *
+ * Default time-zone for tz types is specified with 'tzname'.  If 'tzname' is
+ * NULL and the input string does not contain zone components then "missing tz"
+ * error is thrown.
+ */
+Datum
+parse_datetime(text *date_txt, text *fmt, char *tzname, bool strict, Oid *typid,
+			   int32 *typmod, int *tz)
+{
+	struct pg_tm tm;
+	fsec_t		fsec;
+	int			fprec = 0;
+	int			flags;
+
+	do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags);
+
+	*typmod = fprec ? fprec : -1;	/* fractional part precision */
+
+	if (flags & DCH_DATED)
+	{
+		if (flags & DCH_TIMED)
+		{
+			if (flags & DCH_ZONED)
+			{
+				TimestampTz result;
+
+				if (tm.tm_zone)
+					tzname = (char *) tm.tm_zone;
+
+				if (tzname)
+				{
+					int			dterr = DecodeTimezone(tzname, tz);
+
+					if (dterr)
+						DateTimeParseError(dterr, tzname, "timestamptz");
+				}
+				else
+				{
+					if (*tz == PG_INT32_MIN)
+						ereport(ERROR,
+								(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								 errmsg("missing time-zone in input string for type timestamptz")));
+				}
+
+				if (tm2timestamp(&tm, fsec, tz, &result) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("timestamptz out of range")));
+
+				AdjustTimestampForTypmod(&result, *typmod);
+
+				*typid = TIMESTAMPTZOID;
+				return TimestampTzGetDatum(result);
+			}
+			else
+			{
+				Timestamp	result;
+
+				if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("timestamp out of range")));
+
+				AdjustTimestampForTypmod(&result, *typmod);
+
+				*typid = TIMESTAMPOID;
+				return TimestampGetDatum(result);
+			}
+		}
+		else
+		{
+			if (flags & DCH_ZONED)
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+						 errmsg("datetime format is zoned but not timed")));
+			}
+			else
+			{
+				DateADT		result;
+
+				/* Prevent overflow in Julian-day routines */
+				if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("date out of range: \"%s\"",
+									text_to_cstring(date_txt))));
+
+				result = 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(result))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("date out of range: \"%s\"",
+									text_to_cstring(date_txt))));
+
+				*typid = DATEOID;
+				return DateADTGetDatum(result);
+			}
+		}
+	}
+	else if (flags & DCH_TIMED)
+	{
+		if (flags & DCH_ZONED)
+		{
+			TimeTzADT  *result = palloc(sizeof(TimeTzADT));
+
+			if (tm.tm_zone)
+				tzname = (char *) tm.tm_zone;
+
+			if (tzname)
+			{
+				int			dterr = DecodeTimezone(tzname, tz);
+
+				if (dterr)
+					DateTimeParseError(dterr, tzname, "timetz");
+			}
+			else
+			{
+				if (*tz == PG_INT32_MIN)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							 errmsg("missing time-zone in input string for type timetz")));
+			}
+
+			if (tm2timetz(&tm, fsec, *tz, result) != 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("timetz out of range")));
+
+			AdjustTimeForTypmod(&result->time, *typmod);
+
+			*typid = TIMETZOID;
+			return TimeTzADTPGetDatum(result);
+		}
+		else
+		{
+			TimeADT		result;
+
+			if (tm2time(&tm, fsec, &result) != 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("time out of range")));
+
+			AdjustTimeForTypmod(&result, *typmod);
+
+			*typid = TIMEOID;
+			return TimeADTGetDatum(result);
+		}
+	}
+	else
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+				 errmsg("datetime format is not dated and not timed")));
+	}
+
+	return (Datum) 0;
+}
+
 /*
  * do_to_timestamp: shared code for to_timestamp and to_date
  *
  * Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm
- * and fractional seconds.
+ * and fractional seconds and fractional precision.
  *
  * We parse 'fmt' into a list of FormatNodes, which is then passed to
  * DCH_from_char to populate a TmFromChar with the parsed contents of
@@ -3757,10 +4105,15 @@ to_date(PG_FUNCTION_ARGS)
  *
  * The TmFromChar is then analysed and converted into the final results in
  * struct 'tm' and 'fsec'.
+ *
+ * Bit mask of date/time/zone components found in 'fmt' is returned in 'flags'.
+ *
+ * 'strict' enables error reporting on unmatched trailing characters in input or
+ * format strings patterns.
  */
 static void
-do_to_timestamp(text *date_txt, text *fmt,
-				struct pg_tm *tm, fsec_t *fsec)
+do_to_timestamp(text *date_txt, text *fmt, bool strict,
+				struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags)
 {
 	FormatNode *format;
 	TmFromChar	tmfc;
@@ -3813,9 +4166,13 @@ do_to_timestamp(text *date_txt, text *fmt,
 		/* dump_index(DCH_keywords, DCH_index); */
 #endif
 
-		DCH_from_char(format, date_str, &tmfc);
+		DCH_from_char(format, date_str, &tmfc, strict);
 
 		pfree(fmt_str);
+
+		if (flags)
+			*flags = DCH_datetime_type(format);
+
 		if (!incache)
 			pfree(format);
 	}
@@ -3997,6 +4354,8 @@ do_to_timestamp(text *date_txt, text *fmt,
 		*fsec += tmfc.ms * 1000;
 	if (tmfc.us)
 		*fsec += tmfc.us;
+	if (fprec)
+		*fprec = tmfc.ff;		/* fractional precision, if specified */
 
 	/* Range-check date fields according to bit mask computed above */
 	if (fmask != 0)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index e5ac371fa0f..4d00e996b22 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -70,7 +70,6 @@ typedef struct
 
 static TimeOffset time2t(const int hour, const int min, const int sec, const fsec_t fsec);
 static Timestamp dt2local(Timestamp dt, int timezone);
-static void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
 static void AdjustIntervalForTypmod(Interval *interval, int32 typmod);
 static TimestampTz timestamp2timestamptz(Timestamp timestamp);
 static Timestamp timestamptz2timestamp(TimestampTz timestamp);
@@ -341,7 +340,7 @@ timestamp_scale(PG_FUNCTION_ARGS)
  * AdjustTimestampForTypmod --- round off a timestamp to suit given typmod
  * Works for either timestamp or timestamptz.
  */
-static void
+void
 AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
 {
 	static const int64 TimestampScales[MAX_TIMESTAMP_PRECISION + 1] = {
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index bec129aff1c..bd15bfa5bb0 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -76,5 +76,8 @@ extern TimeTzADT *GetSQLCurrentTime(int32 typmod);
 extern TimeADT GetSQLLocalTime(int32 typmod);
 extern int	time2tm(TimeADT time, struct pg_tm *tm, fsec_t *fsec);
 extern int	timetz2tm(TimeTzADT *time, struct pg_tm *tm, fsec_t *fsec, int *tzp);
+extern int	tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
+extern int	tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
+extern void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
 
 #endif							/* DATE_H */
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index 4de78ebe366..3c04e21b799 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -338,4 +338,6 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
 												   int n);
 extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
 
+extern void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
+
 #endif							/* DATETIME_H */
diff --git a/src/include/utils/formatting.h b/src/include/utils/formatting.h
index 5b275dc9850..38346744452 100644
--- a/src/include/utils/formatting.h
+++ b/src/include/utils/formatting.h
@@ -28,4 +28,7 @@ extern char *asc_tolower(const char *buff, size_t nbytes);
 extern char *asc_toupper(const char *buff, size_t nbytes);
 extern char *asc_initcap(const char *buff, size_t nbytes);
 
+extern Datum parse_datetime(text *date_txt, text *fmt_txt, char *tzname,
+							bool strict, Oid *typid, int32 *typmod, int *tz);
+
 #endif
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index b2b45773339..74ecb7c10e6 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -2786,6 +2786,85 @@ SELECT to_timestamp('2011-12-18 11:38 20',     'YYYY-MM-DD HH12:MI TZM');
  Sun Dec 18 03:18:00 2011 PST
 (1 row)
 
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |         to_timestamp         
+---+------------------------------
+ 1 | Fri Nov 02 12:34:56 2018 PDT
+ 2 | Fri Nov 02 12:34:56 2018 PDT
+ 3 | Fri Nov 02 12:34:56 2018 PDT
+ 4 | Fri Nov 02 12:34:56 2018 PDT
+ 5 | Fri Nov 02 12:34:56 2018 PDT
+ 6 | Fri Nov 02 12:34:56 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |          to_timestamp          
+---+--------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.1 2018 PDT
+ 3 | Fri Nov 02 12:34:56.1 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |          to_timestamp           
+---+---------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.12 2018 PDT
+ 4 | Fri Nov 02 12:34:56.12 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |           to_timestamp           
+---+----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.123 2018 PDT
+ 5 | Fri Nov 02 12:34:56.123 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |           to_timestamp            
+---+-----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1234 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |            to_timestamp            
+---+------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12345 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12345 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |            to_timestamp             
+---+-------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12346 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123456 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ERROR:  date/time field value out of range: "2018-11-02 12:34:56.123456789"
 --
 -- Check handling of multiple spaces in format and/or input
 --
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index b2b171f5601..6bebb4a110e 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -1597,6 +1597,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
             | 2001 1 1 1 1 1 1
 (65 rows)
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamp),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+ to_char_12 |                              to_char                               
+------------+--------------------------------------------------------------------
+            | 0 00 000 0000 00000 000000  0 00 000 0000 00000 000000  000 000000
+            | 7 78 780 7800 78000 780000  7 78 780 7800 78000 780000  780 780000
+            | 7 78 789 7890 78901 789010  7 78 789 7890 78901 789010  789 789010
+            | 7 78 789 7890 78901 789012  7 78 789 7890 78901 789012  789 789012
+(4 rows)
+
 -- timestamp numeric fields constructor
 SELECT make_timestamp(2014,12,28,6,30,45.887);
         make_timestamp        
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 8a4c7199934..cdd3c1401ed 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -1717,6 +1717,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
             | 2001 1 1 1 1 1 1
 (66 rows)
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamptz),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+ to_char_12 |                              to_char                               
+------------+--------------------------------------------------------------------
+            | 0 00 000 0000 00000 000000  0 00 000 0000 00000 000000  000 000000
+            | 7 78 780 7800 78000 780000  7 78 780 7800 78000 780000  780 780000
+            | 7 78 789 7890 78901 789010  7 78 789 7890 78901 789010  789 789010
+            | 7 78 789 7890 78901 789012  7 78 789 7890 78901 789012  789 789012
+(4 rows)
+
 -- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
 SET timezone = '00:00';
 SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index e356dd563ee..3c8580397ac 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -402,6 +402,15 @@ SELECT to_timestamp('2011-12-18 11:38 +05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
 SELECT to_timestamp('2011-12-18 11:38 -05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
 SELECT to_timestamp('2011-12-18 11:38 20',     'YYYY-MM-DD HH12:MI TZM');
 
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+
 --
 -- Check handling of multiple spaces in format and/or input
 --
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index 150eb54c870..dcc5ff61f32 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -228,5 +228,13 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
 SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
    FROM TIMESTAMP_TBL;
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamp),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+
 -- timestamp numeric fields constructor
 SELECT make_timestamp(2014,12,28,6,30,45.887);
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index c3bd46c2331..588c3e033fa 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -252,6 +252,14 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
 SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
    FROM TIMESTAMPTZ_TBL;
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamptz),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+
 -- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
 SET timezone = '00:00';
 SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
0003-error-suppression-for-datetime-1.patchapplication/octet-stream; name=0003-error-suppression-for-datetime-1.patchDownload
commit 626e99175b63c11282b73e62fa8bcfaadb55a22c
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Mon May 27 10:29:07 2019 +0300

    Error suppression support for upcoming jsonpath .datetime() method
    
    Add support of error suppression in some date and time manipulation functions
    as it's required for jsonpath .datetime() method support.  This commit doesn't
    use PG_TRY()/PG_CATCH() in order to implement that.  Instead, it provides
    internal versions of date and time functions used, which support error
    suppression.

diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 4005f6b26db..f8216aa94d0 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -558,13 +558,15 @@ date_mii(PG_FUNCTION_ARGS)
 	PG_RETURN_DATEADT(result);
 }
 
+
 /*
- * Internal routines for promoting date to timestamp and timestamp with
- * time zone
+ * Promote date to timestamp.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero returned.
  */
-
-static Timestamp
-date2timestamp(DateADT dateVal)
+Timestamp
+date2timestamp_opt_error(DateADT dateVal, bool *have_error)
 {
 	Timestamp	result;
 
@@ -580,9 +582,19 @@ date2timestamp(DateADT dateVal)
 		 * boundary need be checked for overflow.
 		 */
 		if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("date out of range for timestamp")));
+		{
+			if (have_error)
+			{
+				*have_error = true;
+				return (Timestamp) 0;
+			}
+			else
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("date out of range for timestamp")));
+			}
+		}
 
 		/* date is days since 2000, timestamp is microseconds since same... */
 		result = dateVal * USECS_PER_DAY;
@@ -591,8 +603,23 @@ date2timestamp(DateADT dateVal)
 	return result;
 }
 
+/*
+ * Single-argument version of date2timestamp_opt_error().
+ */
 static TimestampTz
-date2timestamptz(DateADT dateVal)
+date2timestamp(DateADT dateVal)
+{
+	return date2timestamp_opt_error(dateVal, NULL);
+}
+
+/*
+ * Promote date to timestamp with time zone.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero returned.
+ */
+TimestampTz
+date2timestamptz_opt_error(DateADT dateVal, bool *have_error)
 {
 	TimestampTz result;
 	struct pg_tm tt,
@@ -611,9 +638,19 @@ date2timestamptz(DateADT dateVal)
 		 * boundary need be checked for overflow.
 		 */
 		if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("date out of range for timestamp")));
+		{
+			if (have_error)
+			{
+				*have_error = true;
+				return (TimestampTz) 0;
+			}
+			else
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("date out of range for timestamp")));
+			}
+		}
 
 		j2date(dateVal + POSTGRES_EPOCH_JDATE,
 			   &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday));
@@ -629,14 +666,33 @@ date2timestamptz(DateADT dateVal)
 		 * of time zone, check for allowed timestamp range after adding tz.
 		 */
 		if (!IS_VALID_TIMESTAMP(result))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("date out of range for timestamp")));
+		{
+			if (have_error)
+			{
+				*have_error = true;
+				return (TimestampTz) 0;
+			}
+			else
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("date out of range for timestamp")));
+			}
+		}
 	}
 
 	return result;
 }
 
+/*
+ * Single-argument version of date2timestamptz_opt_error().
+ */
+static TimestampTz
+date2timestamptz(DateADT dateVal)
+{
+	return date2timestamptz_opt_error(dateVal, NULL);
+}
+
 /*
  * date2timestamp_no_overflow
  *
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 1e55cd4ea7b..620aabd3d51 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -98,6 +98,43 @@
 #include "utils/numeric.h"
 #include "utils/pg_locale.h"
 
+/* ----------
+ * Convinience macros for error handling
+ * ----------
+ *
+ * Two macros below helps handling errors in functions, which takes
+ * 'bool *have_error' argument.  When this argument is not NULL, it's expected
+ * that function will suppress ereports when possible.  Instead it should
+ * return some default value and set *have_error flag.
+ *
+ * RETURN_ERROR() macro intended to wrap ereport() calls.  When have_error
+ * argument is provided, then instead of ereport'ing we set *have_error flag
+ * and go to on_error label.  It's supposed that jump resources will be freed
+ * and some 'default' value returned.
+ *
+ * CHECK_ERROR() jumps on_error label when *have_error flag is defined and set.
+ * It's supposed to be used for immediate exit from the function on error
+ * after call of another function with 'bool *have_error' argument.
+ */
+#define RETURN_ERROR(throw_error) \
+do { \
+	if (have_error) \
+	{ \
+		*have_error = true; \
+		goto on_error; \
+	} \
+	else \
+	{ \
+		throw_error; \
+	} \
+} while (0)
+
+#define CHECK_ERROR \
+do { \
+	if (have_error && *have_error) \
+		goto on_error; \
+} while (0)
+
 /* ----------
  * Routines type
  * ----------
@@ -1008,7 +1045,7 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 static void DCH_to_char(FormatNode *node, bool is_interval,
 						TmToChar *in, char *out, Oid collid);
 static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
-						  bool strict);
+						  bool strict, bool *have_error);
 
 #ifdef DEBUG_TO_FROM_CHAR
 static void dump_index(const KeyWord *k, const int *index);
@@ -1019,14 +1056,21 @@ static const char *get_th(char *num, int type);
 static char *str_numth(char *dest, char *num, int type);
 static int	adjust_partial_year_to_2020(int year);
 static int	strspace_len(char *str);
-static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode);
-static void from_char_set_int(int *dest, const int value, const FormatNode *node);
-static int	from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node);
-static int	from_char_parse_int(int *dest, char **src, FormatNode *node);
+static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode,
+							   bool *have_error);
+static void from_char_set_int(int *dest, const int value, const FormatNode *node,
+							  bool *have_error);
+static int	from_char_parse_int_len(int *dest, char **src, const int len,
+									FormatNode *node, bool *have_error);
+static int	from_char_parse_int(int *dest, char **src, FormatNode *node,
+								bool *have_error);
 static int	seq_search(char *name, const char *const *array, int type, int max, int *len);
-static int	from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
+static int	from_char_seq_search(int *dest, char **src,
+								 const char *const *array, int type, int max,
+								 FormatNode *node, bool *have_error);
 static void do_to_timestamp(text *date_txt, text *fmt, bool strict,
-							struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags);
+							struct pg_tm *tm, fsec_t *fsec, int *fprec,
+							int *flags, bool *have_error);
 static char *fill_str(char *str, int c, int max);
 static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
 static char *int_to_roman(int number);
@@ -2202,21 +2246,26 @@ strspace_len(char *str)
  *
  * Puke if the date mode has already been set, and the caller attempts to set
  * it to a conflicting mode.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
-from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode)
+from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode, bool *have_error)
 {
 	if (mode != FROM_CHAR_DATE_NONE)
 	{
 		if (tmfc->mode == FROM_CHAR_DATE_NONE)
 			tmfc->mode = mode;
 		else if (tmfc->mode != mode)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("invalid combination of date conventions"),
-					 errhint("Do not mix Gregorian and ISO week date "
-							 "conventions in a formatting template.")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("invalid combination of date conventions"),
+								  errhint("Do not mix Gregorian and ISO week date "
+										  "conventions in a formatting template."))));
 	}
+
+on_error:
+	return;
 }
 
 /*
@@ -2224,18 +2273,25 @@ from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode)
  *
  * Puke if the destination integer has previously been set to some other
  * non-zero value.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
-from_char_set_int(int *dest, const int value, const FormatNode *node)
+from_char_set_int(int *dest, const int value, const FormatNode *node,
+				  bool *have_error)
 {
 	if (*dest != 0 && *dest != value)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("conflicting values for \"%s\" field in formatting string",
-						node->key->name),
-				 errdetail("This value contradicts a previous setting for "
-						   "the same field type.")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("conflicting values for \"%s\" field in "
+									 "formatting string",
+									 node->key->name),
+							  errdetail("This value contradicts a previous setting "
+										"for the same field type."))));
 	*dest = value;
+
+on_error:
+	return;
 }
 
 /*
@@ -2257,9 +2313,13 @@ from_char_set_int(int *dest, const int value, const FormatNode *node)
  * Note that from_char_parse_int() provides a more convenient wrapper where
  * the length of the field is the same as the length of the format keyword (as
  * with DD and MI).
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and -1 is returned.
  */
 static int
-from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
+from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node,
+						bool *have_error)
 {
 	long		result;
 	char		copy[DCH_MAX_ITEM_SIZ + 1];
@@ -2292,51 +2352,60 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
 		char	   *last;
 
 		if (used < len)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("source string too short for \"%s\" formatting field",
-							node->key->name),
-					 errdetail("Field requires %d characters, but only %d "
-							   "remain.",
-							   len, used),
-					 errhint("If your source string is not fixed-width, try "
-							 "using the \"FM\" modifier.")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("source string too short for \"%s\" "
+										 "formatting field",
+										 node->key->name),
+								  errdetail("Field requires %d characters, "
+											"but only %d remain.",
+											len, used),
+								  errhint("If your source string is not fixed-width, "
+										  "try using the \"FM\" modifier."))));
 
 		errno = 0;
 		result = strtol(copy, &last, 10);
 		used = last - copy;
 
 		if (used > 0 && used < len)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("invalid value \"%s\" for \"%s\"",
-							copy, node->key->name),
-					 errdetail("Field requires %d characters, but only %d "
-							   "could be parsed.", len, used),
-					 errhint("If your source string is not fixed-width, try "
-							 "using the \"FM\" modifier.")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("invalid value \"%s\" for \"%s\"",
+										 copy, node->key->name),
+								  errdetail("Field requires %d characters, "
+											"but only %d could be parsed.",
+											len, used),
+								  errhint("If your source string is not fixed-width, "
+										  "try using the \"FM\" modifier."))));
 
 		*src += used;
 	}
 
 	if (*src == init)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("invalid value \"%s\" for \"%s\"",
-						copy, node->key->name),
-				 errdetail("Value must be an integer.")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("invalid value \"%s\" for \"%s\"",
+									 copy, node->key->name),
+							  errdetail("Value must be an integer."))));
 
 	if (errno == ERANGE || result < INT_MIN || result > INT_MAX)
-		ereport(ERROR,
-				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-				 errmsg("value for \"%s\" in source string is out of range",
-						node->key->name),
-				 errdetail("Value must be in the range %d to %d.",
-						   INT_MIN, INT_MAX)));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							  errmsg("value for \"%s\" in source string is out of range",
+									 node->key->name),
+							  errdetail("Value must be in the range %d to %d.",
+										INT_MIN, INT_MAX))));
 
 	if (dest != NULL)
-		from_char_set_int(dest, (int) result, node);
+	{
+		from_char_set_int(dest, (int) result, node, have_error);
+		CHECK_ERROR;
+	}
+
 	return *src - init;
+
+on_error:
+	return -1;
 }
 
 /*
@@ -2349,9 +2418,9 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
  * required length explicitly.
  */
 static int
-from_char_parse_int(int *dest, char **src, FormatNode *node)
+from_char_parse_int(int *dest, char **src, FormatNode *node, bool *have_error)
 {
-	return from_char_parse_int_len(dest, src, node->key->len, node);
+	return from_char_parse_int_len(dest, src, node->key->len, node, have_error);
 }
 
 /* ----------
@@ -2434,11 +2503,12 @@ seq_search(char *name, const char *const *array, int type, int max, int *len)
  * pointed to by 'dest', advance 'src' to the end of the part of the string
  * which matched, and return the number of characters consumed.
  *
- * If the string doesn't match, throw an error.
+ * If the string doesn't match, throw an error if 'have_error' is NULL,
+ * otherwise set '*have_error' and return -1.
  */
 static int
-from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max,
-					 FormatNode *node)
+from_char_seq_search(int *dest, char **src, const char *const *array, int type,
+					 int max, FormatNode *node, bool *have_error)
 {
 	int			len;
 
@@ -2450,15 +2520,18 @@ from_char_seq_search(int *dest, char **src, const char *const *array, int type,
 		Assert(max <= DCH_MAX_ITEM_SIZ);
 		strlcpy(copy, *src, max + 1);
 
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("invalid value \"%s\" for \"%s\"",
-						copy, node->key->name),
-				 errdetail("The given value did not match any of the allowed "
-						   "values for this field.")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("invalid value \"%s\" for \"%s\"",
+									 copy, node->key->name),
+							  errdetail("The given value did not match any of "
+										"the allowed values for this field."))));
 	}
 	*src += len;
 	return len;
+
+on_error:
+	return -1;
 }
 
 /* ----------
@@ -3060,10 +3133,13 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
  *
  * Note: we currently don't have any to_interval() function, so there
  * is no need here for INVALID_FOR_INTERVAL checks.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  * ----------
  */
 static void
-DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
+DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict,
+			  bool *have_error)
 {
 	FormatNode *n;
 	char	   *s;
@@ -3146,7 +3222,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 			continue;
 		}
 
-		from_char_set_mode(out, n->key->date_mode);
+		from_char_set_mode(out, n->key->date_mode, have_error);
+		CHECK_ERROR;
 
 		switch (n->key->id)
 		{
@@ -3158,8 +3235,10 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 			case DCH_a_m:
 			case DCH_p_m:
 				from_char_seq_search(&value, &s, ampm_strings_long,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->pm, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->pm, value % 2, n, have_error);
+				CHECK_ERROR;
 				out->clock = CLOCK_12_HOUR;
 				break;
 			case DCH_AM:
@@ -3167,30 +3246,37 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 			case DCH_am:
 			case DCH_pm:
 				from_char_seq_search(&value, &s, ampm_strings,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->pm, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->pm, value % 2, n, have_error);
+				CHECK_ERROR;
 				out->clock = CLOCK_12_HOUR;
 				break;
 			case DCH_HH:
 			case DCH_HH12:
-				from_char_parse_int_len(&out->hh, &s, 2, n);
+				from_char_parse_int_len(&out->hh, &s, 2, n, have_error);
+				CHECK_ERROR;
 				out->clock = CLOCK_12_HOUR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_HH24:
-				from_char_parse_int_len(&out->hh, &s, 2, n);
+				from_char_parse_int_len(&out->hh, &s, 2, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_MI:
-				from_char_parse_int(&out->mi, &s, n);
+				from_char_parse_int(&out->mi, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_SS:
-				from_char_parse_int(&out->ss, &s, n);
+				from_char_parse_int(&out->ss, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_MS:		/* millisecond */
-				len = from_char_parse_int_len(&out->ms, &s, 3, n);
+				len = from_char_parse_int_len(&out->ms, &s, 3, n, have_error);
+				CHECK_ERROR;
 
 				/*
 				 * 25 is 0.25 and 250 is 0.25 too; 025 is 0.025 and not 0.25
@@ -3211,7 +3297,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 			case DCH_US:		/* microsecond */
 				len = from_char_parse_int_len(&out->us, &s,
 											  n->key->id == DCH_US ? 6 :
-											  out->ff, n);
+											  out->ff, n, have_error);
+				CHECK_ERROR;
 
 				out->us *= len == 1 ? 100000 :
 					len == 2 ? 10000 :
@@ -3222,16 +3309,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_SSSS:
-				from_char_parse_int(&out->ssss, &s, n);
+				from_char_parse_int(&out->ssss, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_tz:
 			case DCH_TZ:
 			case DCH_OF:
-				ereport(ERROR,
-						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						 errmsg("formatting field \"%s\" is only supported in to_char",
-								n->key->name)));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									  errmsg("formatting field \"%s\" is only supported in to_char",
+											 n->key->name))));
+				CHECK_ERROR;
 				break;
 			case DCH_TZH:
 
@@ -3255,82 +3344,102 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 						out->tzsign = +1;
 				}
 
-				from_char_parse_int_len(&out->tzh, &s, 2, n);
+				from_char_parse_int_len(&out->tzh, &s, 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_TZM:
 				/* assign positive timezone sign if TZH was not seen before */
 				if (!out->tzsign)
 					out->tzsign = +1;
-				from_char_parse_int_len(&out->tzm, &s, 2, n);
+				from_char_parse_int_len(&out->tzm, &s, 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_A_D:
 			case DCH_B_C:
 			case DCH_a_d:
 			case DCH_b_c:
 				from_char_seq_search(&value, &s, adbc_strings_long,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->bc, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->bc, value % 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_AD:
 			case DCH_BC:
 			case DCH_ad:
 			case DCH_bc:
 				from_char_seq_search(&value, &s, adbc_strings,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->bc, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->bc, value % 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_MONTH:
 			case DCH_Month:
 			case DCH_month:
 				from_char_seq_search(&value, &s, months_full, ONE_UPPER,
-									 MAX_MONTH_LEN, n);
-				from_char_set_int(&out->mm, value + 1, n);
+									 MAX_MONTH_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, value + 1, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_MON:
 			case DCH_Mon:
 			case DCH_mon:
 				from_char_seq_search(&value, &s, months, ONE_UPPER,
-									 MAX_MON_LEN, n);
-				from_char_set_int(&out->mm, value + 1, n);
+									 MAX_MON_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, value + 1, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_MM:
-				from_char_parse_int(&out->mm, &s, n);
+				from_char_parse_int(&out->mm, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_DAY:
 			case DCH_Day:
 			case DCH_day:
 				from_char_seq_search(&value, &s, days, ONE_UPPER,
-									 MAX_DAY_LEN, n);
-				from_char_set_int(&out->d, value, n);
+									 MAX_DAY_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->d, value, n, have_error);
+				CHECK_ERROR;
 				out->d++;
 				break;
 			case DCH_DY:
 			case DCH_Dy:
 			case DCH_dy:
 				from_char_seq_search(&value, &s, days, ONE_UPPER,
-									 MAX_DY_LEN, n);
-				from_char_set_int(&out->d, value, n);
+									 MAX_DY_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->d, value, n, have_error);
+				CHECK_ERROR;
 				out->d++;
 				break;
 			case DCH_DDD:
-				from_char_parse_int(&out->ddd, &s, n);
+				from_char_parse_int(&out->ddd, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_IDDD:
-				from_char_parse_int_len(&out->ddd, &s, 3, n);
+				from_char_parse_int_len(&out->ddd, &s, 3, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_DD:
-				from_char_parse_int(&out->dd, &s, n);
+				from_char_parse_int(&out->dd, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_D:
-				from_char_parse_int(&out->d, &s, n);
+				from_char_parse_int(&out->d, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_ID:
-				from_char_parse_int_len(&out->d, &s, 1, n);
+				from_char_parse_int_len(&out->d, &s, 1, n, have_error);
+				CHECK_ERROR;
 				/* Shift numbering to match Gregorian where Sunday = 1 */
 				if (++out->d > 7)
 					out->d = 1;
@@ -3338,7 +3447,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 				break;
 			case DCH_WW:
 			case DCH_IW:
-				from_char_parse_int(&out->ww, &s, n);
+				from_char_parse_int(&out->ww, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Q:
@@ -3353,11 +3463,13 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 				 * We still parse the source string for an integer, but it
 				 * isn't stored anywhere in 'out'.
 				 */
-				from_char_parse_int((int *) NULL, &s, n);
+				from_char_parse_int((int *) NULL, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_CC:
-				from_char_parse_int(&out->cc, &s, n);
+				from_char_parse_int(&out->cc, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Y_YYY:
@@ -3369,11 +3481,12 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 
 					matched = sscanf(s, "%d,%03d%n", &millennia, &years, &nch);
 					if (matched < 2)
-						ereport(ERROR,
-								(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-								 errmsg("invalid input string for \"Y,YYY\"")));
+						RETURN_ERROR(ereport(ERROR,
+											 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+											  errmsg("invalid input string for \"Y,YYY\""))));
 					years += (millennia * 1000);
-					from_char_set_int(&out->year, years, n);
+					from_char_set_int(&out->year, years, n, have_error);
+					CHECK_ERROR;
 					out->yysz = 4;
 					s += nch;
 					SKIP_THth(s, n->suffix);
@@ -3381,47 +3494,62 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 				break;
 			case DCH_YYYY:
 			case DCH_IYYY:
-				from_char_parse_int(&out->year, &s, n);
+				from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
 				out->yysz = 4;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_YYY:
 			case DCH_IYY:
-				if (from_char_parse_int(&out->year, &s, n) < 4)
+				len = from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
+				if (len < 4)
 					out->year = adjust_partial_year_to_2020(out->year);
 				out->yysz = 3;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_YY:
 			case DCH_IY:
-				if (from_char_parse_int(&out->year, &s, n) < 4)
+				len = from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
+				if (len < 4)
 					out->year = adjust_partial_year_to_2020(out->year);
 				out->yysz = 2;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Y:
 			case DCH_I:
-				if (from_char_parse_int(&out->year, &s, n) < 4)
+				len = from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
+				if (len < 4)
 					out->year = adjust_partial_year_to_2020(out->year);
 				out->yysz = 1;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_RM:
 				from_char_seq_search(&value, &s, rm_months_upper,
-									 ALL_UPPER, MAX_RM_LEN, n);
-				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n);
+									 ALL_UPPER, MAX_RM_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value,
+								  n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_rm:
 				from_char_seq_search(&value, &s, rm_months_lower,
-									 ALL_LOWER, MAX_RM_LEN, n);
-				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n);
+									 ALL_LOWER, MAX_RM_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value,
+								  n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_W:
-				from_char_parse_int(&out->w, &s, n);
+				from_char_parse_int(&out->w, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_J:
-				from_char_parse_int(&out->j, &s, n);
+				from_char_parse_int(&out->j, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 		}
@@ -3441,19 +3569,22 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 	if (strict)
 	{
 		if (n->type != NODE_TYPE_END)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("input string is too short for datetime format")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("input string is too short for datetime format"))));
 
 		while (*s != '\0' && isspace((unsigned char) *s))
 			s++;
 
 		if (*s != '\0')
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("trailing characters remain in input string after "
-							"datetime format")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("trailing characters remain in input string "
+										 "after datetime format"))));
 	}
+
+on_error:
+	return;
 }
 
 /*
@@ -3474,9 +3605,13 @@ DCH_prevent_counter_overflow(void)
 	}
 }
 
-/* Get mask of date/time/zone components present in format nodes. */
+/*
+ * Get mask of date/time/zone components present in format nodes.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
+ */
 static int
-DCH_datetime_type(FormatNode *node)
+DCH_datetime_type(FormatNode *node, bool *have_error)
 {
 	FormatNode *n;
 	int			flags = 0;
@@ -3517,10 +3652,10 @@ DCH_datetime_type(FormatNode *node)
 			case DCH_tz:
 			case DCH_TZ:
 			case DCH_OF:
-				ereport(ERROR,
-						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						 errmsg("formatting field \"%s\" is only supported in to_char",
-								n->key->name)));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									  errmsg("formatting field \"%s\" is only supported in to_char",
+											 n->key->name))));
 				flags |= DCH_ZONED;
 				break;
 			case DCH_TZH:
@@ -3574,6 +3709,7 @@ DCH_datetime_type(FormatNode *node)
 		}
 	}
 
+on_error:
 	return flags;
 }
 
@@ -3867,7 +4003,7 @@ to_timestamp(PG_FUNCTION_ARGS)
 	fsec_t		fsec;
 	int			fprec;
 
-	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL, NULL);
 
 	/* Use the specified time zone, if any. */
 	if (tm.tm_zone)
@@ -3906,7 +4042,7 @@ to_date(PG_FUNCTION_ARGS)
 	struct pg_tm tm;
 	fsec_t		fsec;
 
-	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL, NULL);
 
 	/* Prevent overflow in Julian-day routines */
 	if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3935,17 +4071,21 @@ to_date(PG_FUNCTION_ARGS)
  * Default time-zone for tz types is specified with 'tzname'.  If 'tzname' is
  * NULL and the input string does not contain zone components then "missing tz"
  * error is thrown.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero value is returned.
  */
 Datum
-parse_datetime(text *date_txt, text *fmt, char *tzname, bool strict, Oid *typid,
-			   int32 *typmod, int *tz)
+parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
+			   int32 *typmod, int *tz, bool *have_error)
 {
 	struct pg_tm tm;
 	fsec_t		fsec;
 	int			fprec = 0;
 	int			flags;
 
-	do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags);
+	do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags, have_error);
+	CHECK_ERROR;
 
 	*typmod = fprec ? fprec : -1;	/* fractional part precision */
 
@@ -3958,27 +4098,30 @@ parse_datetime(text *date_txt, text *fmt, char *tzname, bool strict, Oid *typid,
 				TimestampTz result;
 
 				if (tm.tm_zone)
-					tzname = (char *) tm.tm_zone;
-
-				if (tzname)
 				{
-					int			dterr = DecodeTimezone(tzname, tz);
+					int			dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
 
 					if (dterr)
-						DateTimeParseError(dterr, tzname, "timestamptz");
+						RETURN_ERROR(DateTimeParseError(dterr, text_to_cstring(date_txt), "timestamptz"));
 				}
 				else
 				{
-					if (*tz == PG_INT32_MIN)
-						ereport(ERROR,
-								(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-								 errmsg("missing time-zone in input string for type timestamptz")));
+					/*
+					 * Time zone is present in format string, but not in input
+					 * string.  Assuming do_to_timestamp() triggers no error
+					 * this should be possible only in non-strict case.
+					 */
+					Assert(!strict);
+
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+										  errmsg("missing time-zone in input string for type timestamptz"))));
 				}
 
 				if (tm2timestamp(&tm, fsec, tz, &result) != 0)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("timestamptz out of range")));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("timestamptz out of range"))));
 
 				AdjustTimestampForTypmod(&result, *typmod);
 
@@ -3990,9 +4133,9 @@ parse_datetime(text *date_txt, text *fmt, char *tzname, bool strict, Oid *typid,
 				Timestamp	result;
 
 				if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("timestamp out of range")));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("timestamp out of range"))));
 
 				AdjustTimestampForTypmod(&result, *typmod);
 
@@ -4004,9 +4147,9 @@ parse_datetime(text *date_txt, text *fmt, char *tzname, bool strict, Oid *typid,
 		{
 			if (flags & DCH_ZONED)
 			{
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-						 errmsg("datetime format is zoned but not timed")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+									  errmsg("datetime format is zoned but not timed"))));
 			}
 			else
 			{
@@ -4014,20 +4157,20 @@ parse_datetime(text *date_txt, text *fmt, char *tzname, bool strict, Oid *typid,
 
 				/* Prevent overflow in Julian-day routines */
 				if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("date out of range: \"%s\"",
-									text_to_cstring(date_txt))));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("date out of range: \"%s\"",
+												 text_to_cstring(date_txt)))));
 
 				result = 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(result))
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("date out of range: \"%s\"",
-									text_to_cstring(date_txt))));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("date out of range: \"%s\"",
+												 text_to_cstring(date_txt)))));
 
 				*typid = DATEOID;
 				return DateADTGetDatum(result);
@@ -4041,27 +4184,30 @@ parse_datetime(text *date_txt, text *fmt, char *tzname, bool strict, Oid *typid,
 			TimeTzADT  *result = palloc(sizeof(TimeTzADT));
 
 			if (tm.tm_zone)
-				tzname = (char *) tm.tm_zone;
-
-			if (tzname)
 			{
-				int			dterr = DecodeTimezone(tzname, tz);
+				int			dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
 
 				if (dterr)
-					DateTimeParseError(dterr, tzname, "timetz");
+					RETURN_ERROR(DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz"));
 			}
 			else
 			{
-				if (*tz == PG_INT32_MIN)
-					ereport(ERROR,
-							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-							 errmsg("missing time-zone in input string for type timetz")));
+				/*
+				 * Time zone is present in format string, but not in input
+				 * string.  Assuming do_to_timestamp() triggers no error this
+				 * should be possible only in non-strict case.
+				 */
+				Assert(!strict);
+
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+									  errmsg("missing time-zone in input string for type timetz"))));
 			}
 
 			if (tm2timetz(&tm, fsec, *tz, result) != 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-						 errmsg("timetz out of range")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+									  errmsg("timetz out of range"))));
 
 			AdjustTimeForTypmod(&result->time, *typmod);
 
@@ -4073,9 +4219,9 @@ parse_datetime(text *date_txt, text *fmt, char *tzname, bool strict, Oid *typid,
 			TimeADT		result;
 
 			if (tm2time(&tm, fsec, &result) != 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-						 errmsg("time out of range")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+									  errmsg("time out of range"))));
 
 			AdjustTimeForTypmod(&result, *typmod);
 
@@ -4085,11 +4231,12 @@ parse_datetime(text *date_txt, text *fmt, char *tzname, bool strict, Oid *typid,
 	}
 	else
 	{
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("datetime format is not dated and not timed")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("datetime format is not dated and not timed"))));
 	}
 
+on_error:
 	return (Datum) 0;
 }
 
@@ -4110,16 +4257,19 @@ parse_datetime(text *date_txt, text *fmt, char *tzname, bool strict, Oid *typid,
  *
  * 'strict' enables error reporting on unmatched trailing characters in input or
  * format strings patterns.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
-do_to_timestamp(text *date_txt, text *fmt, bool strict,
-				struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags)
+do_to_timestamp(text *date_txt, text *fmt, bool strict, struct pg_tm *tm,
+				fsec_t *fsec, int *fprec, int *flags, bool *have_error)
 {
-	FormatNode *format;
+	FormatNode *format = NULL;
 	TmFromChar	tmfc;
 	int			fmt_len;
 	char	   *date_str;
 	int			fmask;
+	bool		incache;
 
 	date_str = text_to_cstring(date_txt);
 
@@ -4133,7 +4283,6 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 	if (fmt_len)
 	{
 		char	   *fmt_str;
-		bool		incache;
 
 		fmt_str = text_to_cstring(fmt);
 
@@ -4166,15 +4315,21 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 		/* dump_index(DCH_keywords, DCH_index); */
 #endif
 
-		DCH_from_char(format, date_str, &tmfc, strict);
+		DCH_from_char(format, date_str, &tmfc, strict, have_error);
+		CHECK_ERROR;
 
 		pfree(fmt_str);
 
 		if (flags)
-			*flags = DCH_datetime_type(format);
+			*flags = DCH_datetime_type(format, have_error);
 
 		if (!incache)
+		{
 			pfree(format);
+			format = NULL;
+		}
+
+		CHECK_ERROR;
 	}
 
 	DEBUG_TMFC(&tmfc);
@@ -4203,11 +4358,13 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 	if (tmfc.clock == CLOCK_12_HOUR)
 	{
 		if (tm->tm_hour < 1 || tm->tm_hour > HOURS_PER_DAY / 2)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("hour \"%d\" is invalid for the 12-hour clock",
-							tm->tm_hour),
-					 errhint("Use the 24-hour clock, or give an hour between 1 and 12.")));
+		{
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("hour \"%d\" is invalid for the 12-hour clock",
+										 tm->tm_hour),
+								  errhint("Use the 24-hour clock, or give an hour between 1 and 12."))));
+		}
 
 		if (tmfc.pm && tm->tm_hour < HOURS_PER_DAY / 2)
 			tm->tm_hour += HOURS_PER_DAY / 2;
@@ -4311,9 +4468,11 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 		 */
 
 		if (!tm->tm_year && !tmfc.bc)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("cannot calculate day of year without year information")));
+		{
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("cannot calculate day of year without year information"))));
+		}
 
 		if (tmfc.mode == FROM_CHAR_DATE_ISOWEEK)
 		{
@@ -4370,7 +4529,7 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 			 * said DTERR_MD_FIELD_OVERFLOW, because we don't want to print an
 			 * irrelevant hint about datestyle.
 			 */
-			DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp");
+			RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp"));
 		}
 	}
 
@@ -4379,7 +4538,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 		tm->tm_min < 0 || tm->tm_min >= MINS_PER_HOUR ||
 		tm->tm_sec < 0 || tm->tm_sec >= SECS_PER_MINUTE ||
 		*fsec < INT64CONST(0) || *fsec >= USECS_PER_SEC)
-		DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp");
+	{
+		RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp"));
+	}
 
 	/* Save parsed time-zone into tm->tm_zone if it was specified */
 	if (tmfc.tzsign)
@@ -4388,7 +4549,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 
 		if (tmfc.tzh < 0 || tmfc.tzh > MAX_TZDISP_HOUR ||
 			tmfc.tzm < 0 || tmfc.tzm >= MINS_PER_HOUR)
-			DateTimeParseError(DTERR_TZDISP_OVERFLOW, date_str, "timestamp");
+		{
+			RETURN_ERROR(DateTimeParseError(DTERR_TZDISP_OVERFLOW, date_str, "timestamp"));
+		}
 
 		tz = psprintf("%c%02d:%02d",
 					  tmfc.tzsign > 0 ? '+' : '-', tmfc.tzh, tmfc.tzm);
@@ -4398,6 +4561,11 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 
 	DEBUG_TM(tm);
 
+on_error:
+
+	if (format && !incache)
+		pfree(format);
+
 	pfree(date_str);
 }
 
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 4d00e996b22..3ff78ed5870 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -337,11 +337,11 @@ timestamp_scale(PG_FUNCTION_ARGS)
 }
 
 /*
- * AdjustTimestampForTypmod --- round off a timestamp to suit given typmod
+ * AdjustTimestampForTypmodError --- round off a timestamp to suit given typmod
  * Works for either timestamp or timestamptz.
  */
-void
-AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
+bool
+AdjustTimestampForTypmodError(Timestamp *time, int32 typmod, bool *error)
 {
 	static const int64 TimestampScales[MAX_TIMESTAMP_PRECISION + 1] = {
 		INT64CONST(1000000),
@@ -367,10 +367,18 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
 		&& (typmod != -1) && (typmod != MAX_TIMESTAMP_PRECISION))
 	{
 		if (typmod < 0 || typmod > MAX_TIMESTAMP_PRECISION)
+		{
+			if (error)
+			{
+				*error = true;
+				return false;
+			}
+
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("timestamp(%d) precision must be between %d and %d",
 							typmod, 0, MAX_TIMESTAMP_PRECISION)));
+		}
 
 		if (*time >= INT64CONST(0))
 		{
@@ -383,8 +391,15 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
 					  * TimestampScales[typmod]);
 		}
 	}
+
+	return true;
 }
 
+void
+AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
+{
+	(void) AdjustTimestampForTypmodError(time, typmod, NULL);
+}
 
 /* timestamptz_in()
  * Convert a string to internal form.
@@ -5194,8 +5209,15 @@ timestamp_timestamptz(PG_FUNCTION_ARGS)
 	PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
 }
 
-static TimestampTz
-timestamp2timestamptz(Timestamp timestamp)
+/*
+ * Convert timestamp to timestamp with time zone.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero returned.
+ */
+
+TimestampTz
+timestamp2timestamptz_opt_error(Timestamp timestamp, bool *have_error)
 {
 	TimestampTz result;
 	struct pg_tm tt,
@@ -5204,23 +5226,33 @@ timestamp2timestamptz(Timestamp timestamp)
 	int			tz;
 
 	if (TIMESTAMP_NOT_FINITE(timestamp))
-		result = timestamp;
-	else
-	{
-		if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("timestamp out of range")));
+		return timestamp;
 
+	if (!timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL))
+	{
 		tz = DetermineTimeZoneOffset(tm, session_timezone);
 
-		if (tm2timestamp(tm, fsec, &tz, &result) != 0)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("timestamp out of range")));
+		if (!tm2timestamp(tm, fsec, &tz, &result))
+			return result;
 	}
 
-	return result;
+	if (have_error)
+		*have_error = true;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+				 errmsg("timestamp out of range")));
+
+	return 0;
+}
+
+/*
+ * Single-argument version of timestamp2timestamptz_opt_error().
+ */
+static TimestampTz
+timestamp2timestamptz(Timestamp timestamp)
+{
+	return timestamp2timestamptz_opt_error(timestamp, NULL);
 }
 
 /* timestamptz_timestamp()
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index bd15bfa5bb0..c29f13aaf04 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -70,6 +70,8 @@ typedef struct
 /* date.c */
 extern int32 anytime_typmod_check(bool istz, int32 typmod);
 extern double date2timestamp_no_overflow(DateADT dateVal);
+extern Timestamp date2timestamp_opt_error(DateADT dateVal, bool *have_error);
+extern TimestampTz date2timestamptz_opt_error(DateADT dateVal, bool *have_error);
 extern void EncodeSpecialDate(DateADT dt, char *str);
 extern DateADT GetSQLCurrentDate(void);
 extern TimeTzADT *GetSQLCurrentTime(int32 typmod);
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index 3c04e21b799..6f43bb4d7f2 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -339,5 +339,7 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
 extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
 
 extern void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
+extern bool AdjustTimestampForTypmodError(Timestamp *time, int32 typmod,
+										  bool *error);
 
 #endif							/* DATETIME_H */
diff --git a/src/include/utils/formatting.h b/src/include/utils/formatting.h
index 38346744452..26a626ad051 100644
--- a/src/include/utils/formatting.h
+++ b/src/include/utils/formatting.h
@@ -29,6 +29,7 @@ extern char *asc_toupper(const char *buff, size_t nbytes);
 extern char *asc_initcap(const char *buff, size_t nbytes);
 
 extern Datum parse_datetime(text *date_txt, text *fmt_txt, char *tzname,
-							bool strict, Oid *typid, int32 *typmod, int *tz);
+							bool strict, Oid *typid, int32 *typmod,
+							int *tz, bool *have_error);
 
 #endif
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index ea16190ec3d..e884d4405f5 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -97,6 +97,9 @@ extern int	timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
 /* timestamp comparison works for timestamptz also */
 #define timestamptz_cmp_internal(dt1,dt2)	timestamp_cmp_internal(dt1, dt2)
 
+extern TimestampTz timestamp2timestamptz_opt_error(Timestamp timestamp,
+												   bool *have_error);
+
 extern int	isoweek2j(int year, int week);
 extern void isoweek2date(int woy, int *year, int *mon, int *mday);
 extern void isoweekdate2date(int isoweek, int wday, int *year, int *mon, int *mday);
0004-implement-jsonpath-datetime-1.patchapplication/octet-stream; name=0004-implement-jsonpath-datetime-1.patchDownload
commit 6e1e6ef5194abe1bd7df559346f18dbb1dfae3fd
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Mon May 27 10:33:09 2019 +0300

    Implement jsonpath .datetime() method
    
    This commit implements jsonpath .datetime() method as it's specified in
    SQL/JSON standard.  There are no-argument and single-argument versions of
    this method.  No-argument version selects first of ISO datetime formats
    matching input string.  Single-argument version accepts template string as
    its argument.
    
    Additionally to .datetime() method itself this commit also implements
    comparison ability of resulting date and time values.  There is some difficulty
    because exising jsonb_path_*() functions are immutable, while comparison of
    timezoned and non-timezoned types involves current timezone.  At first, current
    timezone could be changes in session.  Moreover, timezones themselves are not
    immutable and could be updated.  This is why we let existing immutable functions
    throw errors on such non-immutable comparison.  In the same time this commit
    provides jsonb_path_*_tz() functions which are stable and support operations
    involving timezones.

diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 52a6c315840..baa7e834b48 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1242,6 +1242,46 @@ LANGUAGE INTERNAL
 STRICT IMMUTABLE PARALLEL SAFE
 AS 'jsonb_path_query_first';
 
+CREATE OR REPLACE FUNCTION
+  jsonb_path_exists_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                    silent boolean DEFAULT false)
+RETURNS boolean
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_exists_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_match_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                   silent boolean DEFAULT false)
+RETURNS boolean
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_match_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_query_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                   silent boolean DEFAULT false)
+RETURNS SETOF jsonb
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_query_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_query_array_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                         silent boolean DEFAULT false)
+RETURNS jsonb
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_query_array_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_query_first_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                         silent boolean DEFAULT false)
+RETURNS jsonb
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_query_first_tz';
+
 --
 -- The default permissions for functions mean that anyone can execute them.
 -- A number of functions shouldn't be executable by just anyone, but rather
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 10cd38a5315..af79838d103 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -337,12 +337,14 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
 		case jpiPlus:
 		case jpiMinus:
 		case jpiExists:
+		case jpiDatetime:
 			{
 				int32		arg = reserveSpaceForItemPointer(buf);
 
-				chld = flattenJsonPathParseItem(buf, item->value.arg,
-												nestingLevel + argNestingLevel,
-												insideArraySubscript);
+				chld = !item->value.arg ? pos :
+					flattenJsonPathParseItem(buf, item->value.arg,
+											 nestingLevel + argNestingLevel,
+											 insideArraySubscript);
 				*(int32 *) (buf->data + arg) = chld - pos;
 			}
 			break;
@@ -690,6 +692,15 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
 		case jpiDouble:
 			appendBinaryStringInfo(buf, ".double()", 9);
 			break;
+		case jpiDatetime:
+			appendBinaryStringInfo(buf, ".datetime(", 10);
+			if (v->content.arg)
+			{
+				jspGetArg(v, &elem);
+				printJsonPathItem(buf, &elem, false, false);
+			}
+			appendStringInfoChar(buf, ')');
+			break;
 		case jpiKeyValue:
 			appendBinaryStringInfo(buf, ".keyvalue()", 11);
 			break;
@@ -752,6 +763,8 @@ jspOperationName(JsonPathItemType type)
 			return "floor";
 		case jpiCeiling:
 			return "ceiling";
+		case jpiDatetime:
+			return "datetime";
 		default:
 			elog(ERROR, "unrecognized jsonpath item type: %d", type);
 			return NULL;
@@ -887,6 +900,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
 		case jpiPlus:
 		case jpiMinus:
 		case jpiFilter:
+		case jpiDatetime:
 			read_int32(v->content.arg, base, pos);
 			break;
 		case jpiIndexArray:
@@ -911,7 +925,8 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a)
 		   v->type == jpiIsUnknown ||
 		   v->type == jpiExists ||
 		   v->type == jpiPlus ||
-		   v->type == jpiMinus);
+		   v->type == jpiMinus ||
+		   v->type == jpiDatetime);
 
 	jspInitByBuffer(a, v->base, v->content.arg);
 }
@@ -959,6 +974,7 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
 			   v->type == jpiFloor ||
 			   v->type == jpiCeiling ||
 			   v->type == jpiDouble ||
+			   v->type == jpiDatetime ||
 			   v->type == jpiKeyValue ||
 			   v->type == jpiStartsWith);
 
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index 873d64b6304..a674a6e61ac 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -66,6 +66,7 @@
 #include "miscadmin.h"
 #include "regex/regex.h"
 #include "utils/builtins.h"
+#include "utils/datetime.h"
 #include "utils/datum.h"
 #include "utils/formatting.h"
 #include "utils/float.h"
@@ -107,6 +108,7 @@ typedef struct JsonPathExecContext
 										 * ignored */
 	bool		throwErrors;	/* with "false" all suppressible errors are
 								 * suppressed */
+	bool		useTz;
 } JsonPathExecContext;
 
 /* Context for LIKE_REGEX execution. */
@@ -172,7 +174,8 @@ typedef JsonPathBool (*JsonPathPredicateCallback) (JsonPathItem *jsp,
 typedef Numeric (*BinaryArithmFunc) (Numeric num1, Numeric num2, bool *error);
 
 static JsonPathExecResult executeJsonPath(JsonPath *path, Jsonb *vars,
-										  Jsonb *json, bool throwErrors, JsonValueList *result);
+										  Jsonb *json, bool throwErrors,
+										  JsonValueList *result, bool useTz);
 static JsonPathExecResult executeItem(JsonPathExecContext *cxt,
 									  JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
 static JsonPathExecResult executeItemOptUnwrapTarget(JsonPathExecContext *cxt,
@@ -215,6 +218,8 @@ static JsonPathBool executeLikeRegex(JsonPathItem *jsp, JsonbValue *str,
 static JsonPathExecResult executeNumericItemMethod(JsonPathExecContext *cxt,
 												   JsonPathItem *jsp, JsonbValue *jb, bool unwrap, PGFunction func,
 												   JsonValueList *found);
+static JsonPathExecResult executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
+												JsonbValue *jb, JsonValueList *found);
 static JsonPathExecResult executeKeyValueMethod(JsonPathExecContext *cxt,
 												JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
 static JsonPathExecResult appendBoolResult(JsonPathExecContext *cxt,
@@ -226,7 +231,8 @@ static void getJsonPathVariable(JsonPathExecContext *cxt,
 static int	JsonbArraySize(JsonbValue *jb);
 static JsonPathBool executeComparison(JsonPathItem *cmp, JsonbValue *lv,
 									  JsonbValue *rv, void *p);
-static JsonPathBool compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2);
+static JsonPathBool compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2,
+								 bool useTz);
 static int	compareNumeric(Numeric a, Numeric b);
 static JsonbValue *copyJsonbValue(JsonbValue *src);
 static JsonPathExecResult getArrayIndex(JsonPathExecContext *cxt,
@@ -247,6 +253,8 @@ static JsonbValue *JsonbInitBinary(JsonbValue *jbv, Jsonb *jb);
 static int	JsonbType(JsonbValue *jb);
 static JsonbValue *getScalar(JsonbValue *scalar, enum jbvType type);
 static JsonbValue *wrapItemsInArray(const JsonValueList *items);
+static int	compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
+							bool useTz, bool *have_error);
 
 /****************** User interface to JsonPath executor ********************/
 
@@ -262,8 +270,8 @@ static JsonbValue *wrapItemsInArray(const JsonValueList *items);
  *		SQL/JSON.  Regarding jsonb_path_match(), this function doesn't have
  *		an analogy in SQL/JSON, so we define its behavior on our own.
  */
-Datum
-jsonb_path_exists(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_exists_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -277,7 +285,7 @@ jsonb_path_exists(PG_FUNCTION_ARGS)
 		silent = PG_GETARG_BOOL(3);
 	}
 
-	res = executeJsonPath(jp, vars, jb, !silent, NULL);
+	res = executeJsonPath(jp, vars, jb, !silent, NULL, tz);
 
 	PG_FREE_IF_COPY(jb, 0);
 	PG_FREE_IF_COPY(jp, 1);
@@ -288,6 +296,18 @@ jsonb_path_exists(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(res == jperOk);
 }
 
+Datum
+jsonb_path_exists(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_exists_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_exists_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_exists_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_exists_opr
  *		Implementation of operator "jsonb @? jsonpath" (2-argument version of
@@ -297,7 +317,7 @@ Datum
 jsonb_path_exists_opr(PG_FUNCTION_ARGS)
 {
 	/* just call the other one -- it can handle both cases */
-	return jsonb_path_exists(fcinfo);
+	return jsonb_path_exists_internal(fcinfo, false);
 }
 
 /*
@@ -305,8 +325,8 @@ jsonb_path_exists_opr(PG_FUNCTION_ARGS)
  *		Returns jsonpath predicate result item for the specified jsonb value.
  *		See jsonb_path_exists() comment for details regarding error handling.
  */
-Datum
-jsonb_path_match(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_match_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -320,7 +340,7 @@ jsonb_path_match(PG_FUNCTION_ARGS)
 		silent = PG_GETARG_BOOL(3);
 	}
 
-	(void) executeJsonPath(jp, vars, jb, !silent, &found);
+	(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 	PG_FREE_IF_COPY(jb, 0);
 	PG_FREE_IF_COPY(jp, 1);
@@ -344,6 +364,18 @@ jsonb_path_match(PG_FUNCTION_ARGS)
 	PG_RETURN_NULL();
 }
 
+Datum
+jsonb_path_match(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_match_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_match_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_match_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_match_opr
  *		Implementation of operator "jsonb @@ jsonpath" (2-argument version of
@@ -353,7 +385,7 @@ Datum
 jsonb_path_match_opr(PG_FUNCTION_ARGS)
 {
 	/* just call the other one -- it can handle both cases */
-	return jsonb_path_match(fcinfo);
+	return jsonb_path_match_internal(fcinfo, false);
 }
 
 /*
@@ -361,8 +393,8 @@ jsonb_path_match_opr(PG_FUNCTION_ARGS)
  *		Executes jsonpath for given jsonb document and returns result as
  *		rowset.
  */
-Datum
-jsonb_path_query(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_query_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	FuncCallContext *funcctx;
 	List	   *found;
@@ -386,7 +418,7 @@ jsonb_path_query(PG_FUNCTION_ARGS)
 		vars = PG_GETARG_JSONB_P_COPY(2);
 		silent = PG_GETARG_BOOL(3);
 
-		(void) executeJsonPath(jp, vars, jb, !silent, &found);
+		(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 		funcctx->user_fctx = JsonValueListGetList(&found);
 
@@ -407,13 +439,25 @@ jsonb_path_query(PG_FUNCTION_ARGS)
 	SRF_RETURN_NEXT(funcctx, JsonbPGetDatum(JsonbValueToJsonb(v)));
 }
 
+Datum
+jsonb_path_query(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_query_array
  *		Executes jsonpath for given jsonb document and returns result as
  *		jsonb array.
  */
-Datum
-jsonb_path_query_array(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_query_array_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -421,18 +465,30 @@ jsonb_path_query_array(PG_FUNCTION_ARGS)
 	Jsonb	   *vars = PG_GETARG_JSONB_P(2);
 	bool		silent = PG_GETARG_BOOL(3);
 
-	(void) executeJsonPath(jp, vars, jb, !silent, &found);
+	(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 	PG_RETURN_JSONB_P(JsonbValueToJsonb(wrapItemsInArray(&found)));
 }
 
+Datum
+jsonb_path_query_array(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_array_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_array_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_array_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_query_first
  *		Executes jsonpath for given jsonb document and returns first result
  *		item.  If there are no items, NULL returned.
  */
-Datum
-jsonb_path_query_first(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_query_first_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -440,7 +496,7 @@ jsonb_path_query_first(PG_FUNCTION_ARGS)
 	Jsonb	   *vars = PG_GETARG_JSONB_P(2);
 	bool		silent = PG_GETARG_BOOL(3);
 
-	(void) executeJsonPath(jp, vars, jb, !silent, &found);
+	(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 	if (JsonValueListLength(&found) >= 1)
 		PG_RETURN_JSONB_P(JsonbValueToJsonb(JsonValueListHead(&found)));
@@ -448,6 +504,18 @@ jsonb_path_query_first(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 }
 
+Datum
+jsonb_path_query_first(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_first_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_first_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_first_internal(fcinfo, true);
+}
+
 /********************Execute functions for JsonPath**************************/
 
 /*
@@ -471,7 +539,7 @@ jsonb_path_query_first(PG_FUNCTION_ARGS)
  */
 static JsonPathExecResult
 executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors,
-				JsonValueList *result)
+				JsonValueList *result, bool useTz)
 {
 	JsonPathExecContext cxt;
 	JsonPathExecResult res;
@@ -501,6 +569,7 @@ executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors,
 	cxt.lastGeneratedObjectId = vars ? 2 : 1;
 	cxt.innermostArraySize = -1;
 	cxt.throwErrors = throwErrors;
+	cxt.useTz = useTz;
 
 	if (jspStrictAbsenseOfErrors(&cxt) && !result)
 	{
@@ -1029,6 +1098,12 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
 			}
 			break;
 
+		case jpiDatetime:
+			if (unwrap && JsonbType(jb) == jbvArray)
+				return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
+
+			return executeDateTimeMethod(cxt, jsp, jb, found);
+
 		case jpiKeyValue:
 			if (unwrap && JsonbType(jb) == jbvArray)
 				return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
@@ -1215,7 +1290,7 @@ executeBoolItem(JsonPathExecContext *cxt, JsonPathItem *jsp,
 			jspGetLeftArg(jsp, &larg);
 			jspGetRightArg(jsp, &rarg);
 			return executePredicate(cxt, jsp, &larg, &rarg, jb, true,
-									executeComparison, NULL);
+									executeComparison, cxt);
 
 		case jpiStartsWith:		/* 'whole STARTS WITH initial' */
 			jspGetLeftArg(jsp, &larg);	/* 'whole' */
@@ -1707,6 +1782,135 @@ executeNumericItemMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
 	return executeNextItem(cxt, jsp, &next, jb, found, false);
 }
 
+/*
+ * Implementation of .datetime() method.
+ *
+ * Parses string as datetime value.  Particular type is determined runtime.
+ * When argument is provided then it's used as template string.  When no
+ * argument is supplied, first fitting ISO format is selected.
+ */
+static JsonPathExecResult
+executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
+					  JsonbValue *jb, JsonValueList *found)
+{
+	JsonbValue	jbvbuf;
+	Datum		value;
+	text	   *datetime;
+	Oid			typid;
+	int32		typmod = -1;
+	int			tz = 0;
+	bool		hasNext;
+	JsonPathExecResult res = jperNotFound;
+	JsonPathItem elem;
+
+	if (!(jb = getScalar(jb, jbvString)))
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+							  errmsg("invalid argument for SQL/JSON datetime function"),
+							  errdetail("jsonpath item method .%s() is applied to not a string",
+										jspOperationName(jsp->type)))));
+
+	datetime = cstring_to_text_with_len(jb->val.string.val,
+										jb->val.string.len);
+
+	if (jsp->content.arg)
+	{
+		text	   *template;
+		char	   *template_str;
+		int			template_len;
+		bool		have_error = false;
+
+		jspGetArg(jsp, &elem);
+
+		if (elem.type != jpiString)
+			elog(ERROR, "invalid jsonpath item type for .datetime() argument");
+
+		template_str = jspGetString(&elem, &template_len);
+
+		template = cstring_to_text_with_len(template_str,
+											template_len);
+
+		value = parse_datetime(datetime, template, true,
+							   &typid, &typmod, &tz,
+							   jspThrowErrors(cxt) ? NULL : &have_error);
+
+		if (have_error)
+			res = jperError;
+		else
+			res = jperOk;
+	}
+	else
+	{
+		/* Try to recognize one of ISO formats. */
+		static const char *fmt_str[] =
+		{
+			"yyyy-mm-dd HH24:MI:SS TZH:TZM",
+			"yyyy-mm-dd HH24:MI:SS TZH",
+			"yyyy-mm-dd HH24:MI:SS",
+			"yyyy-mm-dd",
+			"HH24:MI:SS TZH:TZM",
+			"HH24:MI:SS TZH",
+			"HH24:MI:SS"
+		};
+
+		/* cache for format texts */
+		static text *fmt_txt[lengthof(fmt_str)] = {0};
+		int			i;
+
+		/* loop until datetime format fits */
+		for (i = 0; i < lengthof(fmt_str); i++)
+		{
+			bool		have_error = false;
+
+			if (!fmt_txt[i])
+			{
+				MemoryContext oldcxt =
+				MemoryContextSwitchTo(TopMemoryContext);
+
+				fmt_txt[i] = cstring_to_text(fmt_str[i]);
+				MemoryContextSwitchTo(oldcxt);
+			}
+
+			value = parse_datetime(datetime, fmt_txt[i], true,
+								   &typid, &typmod, &tz,
+								   &have_error);
+
+			if (!have_error)
+			{
+				res = jperOk;
+				break;
+			}
+		}
+
+		if (res == jperNotFound)
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+								  errmsg("invalid argument for SQL/JSON datetime function"),
+								  errdetail("unrecognized datetime format"),
+								  errhint("use datetime template argument for explicit format specification"))));
+	}
+
+	pfree(datetime);
+
+	if (jperIsError(res))
+		return res;
+
+	hasNext = jspGetNext(jsp, &elem);
+
+	if (!hasNext && !found)
+		return res;
+
+	jb = hasNext ? &jbvbuf : palloc(sizeof(*jb));
+
+	jb->type = jbvDatetime;
+	jb->val.datetime.value = value;
+	jb->val.datetime.typid = typid;
+	jb->val.datetime.typmod = typmod;
+	jb->val.datetime.tz = tz;
+
+	return executeNextItem(cxt, jsp, &elem, jb, found, hasNext);
+}
+
 /*
  * Implementation of .keyvalue() method.
  *
@@ -1967,14 +2171,16 @@ JsonbArraySize(JsonbValue *jb)
 static JsonPathBool
 executeComparison(JsonPathItem *cmp, JsonbValue *lv, JsonbValue *rv, void *p)
 {
-	return compareItems(cmp->type, lv, rv);
+	JsonPathExecContext *cxt = (JsonPathExecContext *) p;
+
+	return compareItems(cmp->type, lv, rv, cxt->useTz);
 }
 
 /*
  * Compare two SQL/JSON items using comparison operation 'op'.
  */
 static JsonPathBool
-compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2)
+compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2, bool useTz)
 {
 	int			cmp;
 	bool		res;
@@ -2016,6 +2222,21 @@ compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2)
 							 jb2->val.string.val, jb2->val.string.len,
 							 DEFAULT_COLLATION_OID);
 			break;
+		case jbvDatetime:
+			{
+				bool		have_error = false;
+
+				cmp = compareDatetime(jb1->val.datetime.value,
+									  jb1->val.datetime.typid,
+									  jb2->val.datetime.value,
+									  jb2->val.datetime.typid,
+									  useTz,
+									  &have_error);
+
+				if (have_error)
+					return jpbUnknown;
+			}
+			break;
 
 		case jbvBinary:
 		case jbvArray:
@@ -2272,3 +2493,205 @@ wrapItemsInArray(const JsonValueList *items)
 
 	return pushJsonbValue(&ps, WJB_END_ARRAY, NULL);
 }
+
+/*
+ * Cross-type comparison of two datetime SQL/JSON items.  If items are
+ * uncomparable, 'error' flag is set.
+ */
+static int
+compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
+				bool useTz, bool *have_error)
+{
+	PGFunction cmpfunc = NULL;
+
+	switch (typid1)
+	{
+		case DATEOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					cmpfunc = date_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					val1 = date2timestamp_opt_error(val1, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("can't convert value from %s to %s without timezone usage",
+										"date", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val1 = date2timestamptz_opt_error(val1, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMEOID:
+			switch (typid2)
+			{
+				case TIMEOID:
+					cmpfunc = time_cmp;
+
+					break;
+
+				case TIMETZOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("can't convert value from %s to %s without timezone usage",
+										"time", "timetz"),
+								 errhint("use *_tz() function for timezone support")));
+					val1 = DirectFunctionCall1(time_timetz, val1);
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case DATEOID:
+				case TIMESTAMPOID:
+				case TIMESTAMPTZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMETZOID:
+			switch (typid2)
+			{
+				case TIMEOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("can't convert value from %s to %s without timezone usage",
+										"time", "timetz"),
+								 errhint("use *_tz() function for timezone support")));
+					val2 = DirectFunctionCall1(time_timetz, val2);
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case TIMETZOID:
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case DATEOID:
+				case TIMESTAMPOID:
+				case TIMESTAMPTZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMESTAMPOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					val2 = date2timestamp_opt_error(val2, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("can't convert value from %s to %s without timezone usage",
+										"timestamp", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val1 = timestamp2timestamptz_opt_error(val1, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMESTAMPTZOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("can't convert value from %s to %s without timezone usage",
+										"date", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val2 = date2timestamptz_opt_error(val2, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("can't convert value from %s to %s without timezone usage",
+										"timestamp", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val2 = timestamp2timestamptz_opt_error(val2, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		default:
+			elog(ERROR, "unrecognized SQL/JSON datetime type oid: %d",
+				 typid1);
+	}
+
+	if (*have_error)
+		return 0;
+
+	if (!cmpfunc)
+		elog(ERROR, "unrecognized SQL/JSON datetime type oid: %d",
+			 typid2);
+
+	*have_error = false;
+
+	return DatumGetInt32(DirectFunctionCall2(cmpfunc, val1, val2));
+}
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
index 22c2089f78f..b2a0203698b 100644
--- a/src/backend/utils/adt/jsonpath_gram.y
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -94,12 +94,14 @@ static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr,
 %token	<str>		LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
 %token	<str>		ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P
 %token	<str>		ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P KEYVALUE_P
+%token	<str>		DATETIME_P
 
 %type	<result>	result
 
 %type	<value>		scalar_value path_primary expr array_accessor
 					any_path accessor_op key predicate delimited_predicate
 					index_elem starts_with_initial expr_or_predicate
+					datetime_template opt_datetime_template
 
 %type	<elems>		accessor_expr
 
@@ -247,9 +249,20 @@ accessor_op:
 	| array_accessor				{ $$ = $1; }
 	| '.' any_path					{ $$ = $2; }
 	| '.' method '(' ')'			{ $$ = makeItemType($2); }
+	| '.' DATETIME_P '(' opt_datetime_template ')'
+									{ $$ = makeItemUnary(jpiDatetime, $4); }
 	| '?' '(' predicate ')'			{ $$ = makeItemUnary(jpiFilter, $3); }
 	;
 
+datetime_template:
+	STRING_P						{ $$ = makeItemString(&$1); }
+	;
+
+opt_datetime_template:
+	datetime_template				{ $$ = $1; }
+	| /* EMPTY */					{ $$ = NULL; }
+	;
+
 key:
 	key_name						{ $$ = makeItemKey(&$1); }
 	;
@@ -272,6 +285,7 @@ key_name:
 	| FLOOR_P
 	| DOUBLE_P
 	| CEILING_P
+	| DATETIME_P
 	| KEYVALUE_P
 	| LAST_P
 	| STARTS_P
diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l
index 2165ffcc254..ced65edb59b 100644
--- a/src/backend/utils/adt/jsonpath_scan.l
+++ b/src/backend/utils/adt/jsonpath_scan.l
@@ -340,6 +340,7 @@ static const JsonPathKeyword keywords[] = {
 	{ 6, false,	STRICT_P,	"strict"},
 	{ 7, false,	CEILING_P,	"ceiling"},
 	{ 7, false,	UNKNOWN_P,	"unknown"},
+	{ 8, false,	DATETIME_P,	"datetime"},
 	{ 8, false,	KEYVALUE_P,	"keyvalue"},
 	{ 10,false, LIKE_REGEX_P, "like_regex"},
 };
diff --git a/src/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index 16f5ca233a9..72876533acf 100644
--- a/src/backend/utils/errcodes.txt
+++ b/src/backend/utils/errcodes.txt
@@ -207,6 +207,7 @@ Section: Class 22 - Data Exception
 2200S    E    ERRCODE_INVALID_XML_COMMENT                                    invalid_xml_comment
 2200T    E    ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION                     invalid_xml_processing_instruction
 22030    E    ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE                        duplicate_json_object_key_value
+22031    E    ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION            invalid_argument_for_json_datetime_function
 22032    E    ERRCODE_INVALID_JSON_TEXT                                      invalid_json_text
 22033    E    ERRCODE_INVALID_JSON_SUBSCRIPT                                 invalid_json_subscript
 22034    E    ERRCODE_MORE_THAN_ONE_JSON_ITEM                                more_than_one_json_item
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 87335248a03..8e4b342c778 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9279,6 +9279,28 @@
   proname => 'jsonb_path_match', prorettype => 'bool',
   proargtypes => 'jsonb jsonpath jsonb bool', prosrc => 'jsonb_path_match' },
 
+{ oid => '6015', descr => 'jsonpath exists test with timezone',
+  proname => 'jsonb_path_exists_tz', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_exists_tz' },
+{ oid => '6016', descr => 'jsonpath query with timezone',
+  proname => 'jsonb_path_query_tz', provolatile => 's',
+  prorows => '1000', proretset => 't',
+  prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_query_tz' },
+{ oid => '6017', descr => 'jsonpath query wrapped into array with timezone',
+  proname => 'jsonb_path_query_array_tz', provolatile => 's',
+  prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_query_array_tz' },
+{ oid => '6018', descr => 'jsonpath query first item with timezone',
+  proname => 'jsonb_path_query_first_tz', provolatile => 's',
+  prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_query_first_tz' },
+{ oid => '6019', descr => 'jsonpath match with timezone',
+  proname => 'jsonb_path_match_tz', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_match_tz' },
+
 { oid => '4010', descr => 'implementation of @? operator',
   proname => 'jsonb_path_exists_opr', prorettype => 'bool',
   proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_path_exists_opr' },
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 3e9d60cb760..24966f3e749 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -79,6 +79,7 @@ typedef enum JsonPathItemType
 	jpiFloor,					/* .floor() item method */
 	jpiCeiling,					/* .ceiling() item method */
 	jpiDouble,					/* .double() item method */
+	jpiDatetime,				/* .datetime() item method */
 	jpiKeyValue,				/* .keyvalue() item method */
 	jpiSubscript,				/* array subscript: 'expr' or 'expr TO expr' */
 	jpiLast,					/* LAST array subscript */
diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
index b486fb602a3..e347a2a0d67 100644
--- a/src/test/regress/expected/jsonb_jsonpath.out
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -1622,6 +1622,532 @@ select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "adc\nabc", "
  "abdacb"
 (2 rows)
 
+select jsonb_path_query('null', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() is applied to not a string
+select jsonb_path_query('true', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() is applied to not a string
+select jsonb_path_query('1', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() is applied to not a string
+select jsonb_path_query('[]', '$.datetime()');
+ jsonb_path_query 
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'strict $.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() is applied to not a string
+select jsonb_path_query('{}', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() is applied to not a string
+select jsonb_path_query('""', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  unrecognized datetime format
+HINT:  use datetime template argument for explicit format specification
+select jsonb_path_query('"12:34"', '$.datetime("aaa")');
+ERROR:  trailing characters remain in input string after datetime format
+select jsonb_path_query('"aaaa"', '$.datetime("HH24")');
+ERROR:  invalid value "aa" for "HH24"
+DETAIL:  Value must be an integer.
+select jsonb '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")';
+ ?column? 
+----------
+ t
+(1 row)
+
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+ jsonb_path_query 
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+ jsonb_path_query 
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+ERROR:  trailing characters remain in input string after datetime format
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+ERROR:  trailing characters remain in input string after datetime format
+select jsonb_path_query('"10-03-2017 12:34"', '       $.datetime("dd-mm-yyyy HH24:MI").type()');
+       jsonb_path_query        
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+      jsonb_path_query      
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+     jsonb_path_query     
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+   jsonb_path_query    
+-----------------------
+ "time with time zone"
+(1 row)
+
+set time zone '+00';
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+ jsonb_path_query 
+------------------
+ "12:34:00"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone '+10';
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+ jsonb_path_query 
+------------------
+ "12:34:00"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone default;
+select jsonb_path_query('"2017-03-10"', '$.datetime().type()');
+ jsonb_path_query 
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"2017-03-10"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+       jsonb_path_query        
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:34:56"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()');
+      jsonb_path_query      
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:56+03:00"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()');
+      jsonb_path_query      
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:56+03:10"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime().type()');
+     jsonb_path_query     
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "12:34:56"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()');
+   jsonb_path_query    
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "12:34:56+03:00"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()');
+   jsonb_path_query    
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "12:34:56+03:10"
+(1 row)
+
+set time zone '+00';
+-- date comparison
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR:  can't convert value from date to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR:  can't convert value from date to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR:  can't convert value from date to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10"
+ "2017-03-10T00:00:00"
+ "2017-03-10T03:00:00+03:00"
+(3 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10"
+ "2017-03-11"
+ "2017-03-10T00:00:00"
+ "2017-03-10T12:34:56"
+ "2017-03-10T03:00:00+03:00"
+(5 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-09"
+ "2017-03-10T01:02:03+04:00"
+(2 rows)
+
+-- time comparison
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+ERROR:  can't convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+ERROR:  can't convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+ERROR:  can't convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00"
+ "12:35:00+00:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00"
+ "12:36:00"
+ "12:35:00+00:00"
+(3 rows)
+
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:34:00"
+ "12:35:00+01:00"
+ "13:35:00+01:00"
+(3 rows)
+
+-- timetz comparison
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR:  can't convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR:  can't convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR:  can't convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00+01:00"
+(1 row)
+
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00+01:00"
+ "12:36:00+01:00"
+ "12:35:00-02:00"
+ "11:35:00"
+ "12:35:00"
+(5 rows)
+
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:34:00+01:00"
+ "12:35:00+02:00"
+ "10:35:00"
+(3 rows)
+
+-- timestamp comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR:  can't convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR:  can't convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR:  can't convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T13:35:00+01:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T12:36:00"
+ "2017-03-10T13:35:00+01:00"
+ "2017-03-10T12:35:00-01:00"
+ "2017-03-11"
+(5 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:34:00"
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10"
+(3 rows)
+
+-- timestamptz comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR:  can't convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR:  can't convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR:  can't convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T11:35:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+set time zone default;
 -- jsonpath operators
 SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
  jsonb_path_query 
diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
index 0f9cd17e2e9..84d41b77155 100644
--- a/src/test/regress/expected/jsonpath.out
+++ b/src/test/regress/expected/jsonpath.out
@@ -401,6 +401,18 @@ select '$.keyvalue().key'::jsonpath;
  $.keyvalue()."key"
 (1 row)
 
+select '$.datetime()'::jsonpath;
+   jsonpath   
+--------------
+ $.datetime()
+(1 row)
+
+select '$.datetime("datetime template")'::jsonpath;
+            jsonpath             
+---------------------------------
+ $.datetime("datetime template")
+(1 row)
+
 select '$ ? (@ starts with "abc")'::jsonpath;
         jsonpath         
 -------------------------
diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
index 464ff94be37..0e9a4f5b233 100644
--- a/src/test/regress/sql/jsonb_jsonpath.sql
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -340,6 +340,178 @@ select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "adc\nabc", "
 select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "adc\nabc", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "m")');
 select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "adc\nabc", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "s")');
 
+select jsonb_path_query('null', '$.datetime()');
+select jsonb_path_query('true', '$.datetime()');
+select jsonb_path_query('1', '$.datetime()');
+select jsonb_path_query('[]', '$.datetime()');
+select jsonb_path_query('[]', 'strict $.datetime()');
+select jsonb_path_query('{}', '$.datetime()');
+select jsonb_path_query('""', '$.datetime()');
+select jsonb_path_query('"12:34"', '$.datetime("aaa")');
+select jsonb_path_query('"aaaa"', '$.datetime("HH24")');
+
+select jsonb '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")';
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+
+select jsonb_path_query('"10-03-2017 12:34"', '       $.datetime("dd-mm-yyyy HH24:MI").type()');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+
+set time zone '+00';
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone '+10';
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone default;
+
+select jsonb_path_query('"2017-03-10"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()');
+select jsonb_path_query('"12:34:56"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56"', '$.datetime()');
+select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56 +3"', '$.datetime()');
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()');
+
+set time zone '+00';
+
+-- date comparison
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+
+-- time comparison
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+
+-- timetz comparison
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+
+-- timestamp comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+
+-- timestamptz comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+
+set time zone default;
+
 -- jsonpath operators
 
 SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
index 9171ddbc6cd..888385a082f 100644
--- a/src/test/regress/sql/jsonpath.sql
+++ b/src/test/regress/sql/jsonpath.sql
@@ -72,6 +72,8 @@ select '"aaa".type()'::jsonpath;
 select 'true.type()'::jsonpath;
 select '$.double().floor().ceiling().abs()'::jsonpath;
 select '$.keyvalue().key'::jsonpath;
+select '$.datetime()'::jsonpath;
+select '$.datetime("datetime template")'::jsonpath;
 
 select '$ ? (@ starts with "abc")'::jsonpath;
 select '$ ? (@ starts with $var)'::jsonpath;
#2Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Alexander Korotkov (#1)
4 attachment(s)
Re: Support for jsonpath .datetime() method

On Tue, May 28, 2019 at 8:55 AM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

Attached patchset implements jsonpath .datetime() method.

Revised patchset is attached. Some inconsistencies around
parse_datetime() function are fixed. Rebased to current master as
well.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0001-datetime-in-JsonbValue-2.patchapplication/octet-stream; name=0001-datetime-in-JsonbValue-2.patchDownload
commit 0845588d81414ba96f2e6ed6003164794800366b
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Thu May 16 21:48:26 2019 +0300

    Allow datetime values in JsonbValue
    
    SQL/JSON standard allows manipulation with datetime values.  So, it appears to
    be convinient to allow datetime values to be represented in JsonbValue struct.
    These datetime values are allowed for temporary representation only.  During
    serialization datetime values are converted into strings.
    
    SQL/JSON requires writing timestamps with timezone in the same timezone offset
    as they were parsed.  This is why we allow storage of timezone offset in
    JsonbValue struct.  For the same reason timezone offset argument is added to
    JsonEncodeDateTime() function.

diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index 26d293709aa..7440f77cbe5 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -1506,7 +1506,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 			{
 				char		buf[MAXDATELEN + 1];
 
-				JsonEncodeDateTime(buf, val, DATEOID);
+				JsonEncodeDateTime(buf, val, DATEOID, NULL);
 				appendStringInfo(result, "\"%s\"", buf);
 			}
 			break;
@@ -1514,7 +1514,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 			{
 				char		buf[MAXDATELEN + 1];
 
-				JsonEncodeDateTime(buf, val, TIMESTAMPOID);
+				JsonEncodeDateTime(buf, val, TIMESTAMPOID, NULL);
 				appendStringInfo(result, "\"%s\"", buf);
 			}
 			break;
@@ -1522,7 +1522,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 			{
 				char		buf[MAXDATELEN + 1];
 
-				JsonEncodeDateTime(buf, val, TIMESTAMPTZOID);
+				JsonEncodeDateTime(buf, val, TIMESTAMPTZOID, NULL);
 				appendStringInfo(result, "\"%s\"", buf);
 			}
 			break;
@@ -1550,10 +1550,11 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 
 /*
  * Encode 'value' of datetime type 'typid' into JSON string in ISO format using
- * optionally preallocated buffer 'buf'.
+ * optionally preallocated buffer 'buf'.  Optional 'tzp' determines time-zone
+ * offset (in seconds) in which we want to show timestamptz.
  */
 char *
-JsonEncodeDateTime(char *buf, Datum value, Oid typid)
+JsonEncodeDateTime(char *buf, Datum value, Oid typid, const int *tzp)
 {
 	if (!buf)
 		buf = palloc(MAXDATELEN + 1);
@@ -1630,11 +1631,30 @@ JsonEncodeDateTime(char *buf, Datum value, Oid typid)
 				const char *tzn = NULL;
 
 				timestamp = DatumGetTimestampTz(value);
+
+				/*
+				 * If time-zone is specified, we apply a time-zone shift,
+				 * convert timestamptz to pg_tm as if it was without
+				 * time-zone, and then use specified time-zone for encoding
+				 * timestamp into a string.
+				 */
+				if (tzp)
+				{
+					tz = *tzp;
+					timestamp -= (TimestampTz) tz * USECS_PER_SEC;
+				}
+
 				/* Same as timestamptz_out(), but forcing DateStyle */
 				if (TIMESTAMP_NOT_FINITE(timestamp))
 					EncodeSpecialTimestamp(timestamp, buf);
-				else if (timestamp2tm(timestamp, &tz, &tm, &fsec, &tzn, NULL) == 0)
+				else if (timestamp2tm(timestamp, tzp ? NULL : &tz, &tm, &fsec,
+									  tzp ? NULL : &tzn, NULL) == 0)
+				{
+					if (tzp)
+						tm.tm_isdst = 1;	/* set time-zone presence flag */
+
 					EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf);
+				}
 				else
 					ereport(ERROR,
 							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 69f41ab4556..74b4bbe44c6 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -206,6 +206,24 @@ JsonbTypeName(JsonbValue *jbv)
 			return "boolean";
 		case jbvNull:
 			return "null";
+		case jbvDatetime:
+			switch (jbv->val.datetime.typid)
+			{
+				case DATEOID:
+					return "date";
+				case TIMEOID:
+					return "time without time zone";
+				case TIMETZOID:
+					return "time with time zone";
+				case TIMESTAMPOID:
+					return "timestamp without time zone";
+				case TIMESTAMPTZOID:
+					return "timestamp with time zone";
+				default:
+					elog(ERROR, "unrecognized jsonb value datetime type: %d",
+						 jbv->val.datetime.typid);
+			}
+			return "unknown";
 		default:
 			elog(ERROR, "unrecognized jsonb value type: %d", jbv->type);
 			return "unknown";
@@ -805,17 +823,20 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result,
 				break;
 			case JSONBTYPE_DATE:
 				jb.type = jbvString;
-				jb.val.string.val = JsonEncodeDateTime(NULL, val, DATEOID);
+				jb.val.string.val = JsonEncodeDateTime(NULL, val,
+													   DATEOID, NULL);
 				jb.val.string.len = strlen(jb.val.string.val);
 				break;
 			case JSONBTYPE_TIMESTAMP:
 				jb.type = jbvString;
-				jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPOID);
+				jb.val.string.val = JsonEncodeDateTime(NULL, val,
+													   TIMESTAMPOID, NULL);
 				jb.val.string.len = strlen(jb.val.string.val);
 				break;
 			case JSONBTYPE_TIMESTAMPTZ:
 				jb.type = jbvString;
-				jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPTZOID);
+				jb.val.string.val = JsonEncodeDateTime(NULL, val,
+													   TIMESTAMPTZOID, NULL);
 				jb.val.string.len = strlen(jb.val.string.val);
 				break;
 			case JSONBTYPE_JSONCAST:
diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index 7969f6f5843..cb2bd872cf5 100644
--- a/src/backend/utils/adt/jsonb_util.c
+++ b/src/backend/utils/adt/jsonb_util.c
@@ -14,9 +14,12 @@
 #include "postgres.h"
 
 #include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
 #include "miscadmin.h"
 #include "utils/builtins.h"
+#include "utils/datetime.h"
 #include "utils/hashutils.h"
+#include "utils/jsonapi.h"
 #include "utils/jsonb.h"
 #include "utils/memutils.h"
 #include "utils/varlena.h"
@@ -241,6 +244,7 @@ compareJsonbContainers(JsonbContainer *a, JsonbContainer *b)
 							res = (va.val.object.nPairs > vb.val.object.nPairs) ? 1 : -1;
 						break;
 					case jbvBinary:
+					case jbvDatetime:
 						elog(ERROR, "unexpected jbvBinary value");
 				}
 			}
@@ -1749,6 +1753,22 @@ convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal)
 				JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE;
 			break;
 
+		case jbvDatetime:
+			{
+				char		buf[MAXDATELEN + 1];
+				size_t		len;
+
+				JsonEncodeDateTime(buf,
+								   scalarVal->val.datetime.value,
+								   scalarVal->val.datetime.typid,
+								   &scalarVal->val.datetime.tz);
+				len = strlen(buf);
+				appendToBuffer(buffer, buf, len);
+
+				*jentry = JENTRY_ISSTRING | len;
+			}
+			break;
+
 		default:
 			elog(ERROR, "invalid jsonb scalar type");
 	}
diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h
index 5f4d479a7b2..b707b09ce31 100644
--- a/src/include/utils/jsonapi.h
+++ b/src/include/utils/jsonapi.h
@@ -161,6 +161,7 @@ extern Jsonb *transform_jsonb_string_values(Jsonb *jsonb, void *action_state,
 extern text *transform_json_string_values(text *json, void *action_state,
 										  JsonTransformStringValuesAction transform_action);
 
-extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid);
+extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid,
+								const int *tzp);
 
 #endif							/* JSONAPI_H */
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index 2fe7d32fec2..ec66d150263 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -241,7 +241,15 @@ enum jbvType
 	jbvArray = 0x10,
 	jbvObject,
 	/* Binary (i.e. struct Jsonb) jbvArray/jbvObject */
-	jbvBinary
+	jbvBinary,
+
+	/*
+	 * Virtual types.
+	 *
+	 * These types are used only for in-memory JSON processing and serialized
+	 * into JSON strings when outputted to json/jsonb.
+	 */
+	jbvDatetime = 0x20,
 };
 
 /*
@@ -282,11 +290,21 @@ struct JsonbValue
 			int			len;
 			JsonbContainer *data;
 		}			binary;		/* Array or object, in on-disk format */
+
+		struct
+		{
+			Datum		value;
+			Oid			typid;
+			int32		typmod;
+			int			tz;		/* Numeric time zone, in seconds for
+								 * TimestampTz data type */
+		}			datetime;
 	}			val;
 };
 
-#define IsAJsonbScalar(jsonbval)	((jsonbval)->type >= jbvNull && \
-									 (jsonbval)->type <= jbvBool)
+#define IsAJsonbScalar(jsonbval)	(((jsonbval)->type >= jbvNull && \
+									  (jsonbval)->type <= jbvBool) || \
+									  (jsonbval)->type == jbvDatetime)
 
 /*
  * Key/value pair within an Object.
0002-datetime-conversion-for-jsonpath-2.patchapplication/octet-stream; name=0002-datetime-conversion-for-jsonpath-2.patchDownload
commit cd3f24e54f19bf4e78f10b3c04addad593bd7a65
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Wed Jan 23 05:55:49 2019 +0300

    Improve datetime conversion for upcoming jsonpath .datetime() method
    
    Jsonpath language (part of SQL/JSON standard) includes functions for datetime
    conversion.  In order to support that, we have to extend our infrastructure
    in following ways.
    
      1. FF1-FF6 format patterns implementing different fractions of second.  FF3
         and FF6 are effectively just synonyms for MS and US.  But other fractions
         were not implemented yet.
      2. to_datetime() internal function, which dynamically determines result
         datatype depending on format string.
      3. Strict parsing mode, which doesn't allow trailing spaces and unmatched
         format patterns.
    
    The first improvement is already user-visible and can use used in datetime
    parsing/printing functions.  Other improvements are internal, they will be
    user-visible together with jsonpath.
    
    Code was written by Nikita Glukhov and Teodor Sigaev, revised by me.
    Documentation was written by Oleg Bartunov and Liudmila Mantrova.  The work
    was inspired by Oleg Bartunov.
    
    Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
    Author: Nikita Glukhov, Teodor Sigaev, Alexander Korotkov, Oleg Bartunov, Liudmila Mantrova
    Reviewed-by: Andrew Dunstan, Pavel Stehule, Tomas Vondra, Alexander Korotkov

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 0be8b8b95d2..b27edc804bd 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -6146,6 +6146,30 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
         <entry><literal>US</literal></entry>
         <entry>microsecond (000000-999999)</entry>
        </row>
+       <row>
+        <entry><literal>FF1</literal></entry>
+        <entry>decisecond (0-9)</entry>
+       </row>
+       <row>
+        <entry><literal>FF2</literal></entry>
+        <entry>centisecond (00-99)</entry>
+       </row>
+       <row>
+        <entry><literal>FF3</literal></entry>
+        <entry>millisecond (000-999)</entry>
+       </row>
+       <row>
+        <entry><literal>FF4</literal></entry>
+        <entry>tenth of a millisecond (0000-9999)</entry>
+       </row>
+       <row>
+        <entry><literal>FF5</literal></entry>
+        <entry>hundredth of a millisecond (00000-99999)</entry>
+       </row>
+       <row>
+        <entry><literal>FF6</literal></entry>
+        <entry>microsecond (000000-999999)</entry>
+       </row>
        <row>
         <entry><literal>SSSS</literal></entry>
         <entry>seconds past midnight (0-86399)</entry>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 4b1afb10f92..9e291b5c7bc 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -41,11 +41,6 @@
 #endif
 
 
-static int	tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
-static int	tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
-static void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
-
-
 /* common code for timetypmodin and timetztypmodin */
 static int32
 anytime_typmodin(bool istz, ArrayType *ta)
@@ -1203,7 +1198,7 @@ time_in(PG_FUNCTION_ARGS)
 /* tm2time()
  * Convert a tm structure to a time data type.
  */
-static int
+int
 tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result)
 {
 	*result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec)
@@ -1379,7 +1374,7 @@ time_scale(PG_FUNCTION_ARGS)
  * have a fundamental tie together but rather a coincidence of
  * implementation. - thomas
  */
-static void
+void
 AdjustTimeForTypmod(TimeADT *time, int32 typmod)
 {
 	static const int64 TimeScales[MAX_TIME_PRECISION + 1] = {
@@ -1957,7 +1952,7 @@ time_part(PG_FUNCTION_ARGS)
 /* tm2timetz()
  * Convert a tm structure to a time data type.
  */
-static int
+int
 tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result)
 {
 	result->time = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) *
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 206576d4bd3..fe6aa91b2e9 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -86,6 +86,7 @@
 #endif
 
 #include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
 #include "mb/pg_wchar.h"
 #include "utils/builtins.h"
 #include "utils/date.h"
@@ -436,7 +437,8 @@ typedef struct
 				clock,			/* 12 or 24 hour clock? */
 				tzsign,			/* +1, -1 or 0 if timezone info is absent */
 				tzh,
-				tzm;
+				tzm,
+				ff;				/* fractional precision */
 } TmFromChar;
 
 #define ZERO_tmfc(_X) memset(_X, 0, sizeof(TmFromChar))
@@ -596,6 +598,12 @@ typedef enum
 	DCH_Day,
 	DCH_Dy,
 	DCH_D,
+	DCH_FF1,
+	DCH_FF2,
+	DCH_FF3,
+	DCH_FF4,
+	DCH_FF5,
+	DCH_FF6,
 	DCH_FX,						/* global suffix */
 	DCH_HH24,
 	DCH_HH12,
@@ -645,6 +653,12 @@ typedef enum
 	DCH_dd,
 	DCH_dy,
 	DCH_d,
+	DCH_ff1,
+	DCH_ff2,
+	DCH_ff3,
+	DCH_ff4,
+	DCH_ff5,
+	DCH_ff6,
 	DCH_fx,
 	DCH_hh24,
 	DCH_hh12,
@@ -745,7 +759,13 @@ static const KeyWord DCH_keywords[] = {
 	{"Day", 3, DCH_Day, false, FROM_CHAR_DATE_NONE},
 	{"Dy", 2, DCH_Dy, false, FROM_CHAR_DATE_NONE},
 	{"D", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
-	{"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},	/* F */
+	{"FF1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE},	/* F */
+	{"FF2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE},
+	{"FF3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE},
+	{"FF4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE},
+	{"FF5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE},
+	{"FF6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE},
+	{"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
 	{"HH24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE},	/* H */
 	{"HH12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
 	{"HH", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
@@ -794,7 +814,13 @@ static const KeyWord DCH_keywords[] = {
 	{"dd", 2, DCH_DD, true, FROM_CHAR_DATE_GREGORIAN},
 	{"dy", 2, DCH_dy, false, FROM_CHAR_DATE_NONE},
 	{"d", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
-	{"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},	/* f */
+	{"ff1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE},	/* f */
+	{"ff2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE},
+	{"ff3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE},
+	{"ff4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE},
+	{"ff5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE},
+	{"ff6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE},
+	{"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
 	{"hh24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE},	/* h */
 	{"hh12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
 	{"hh", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
@@ -895,10 +921,10 @@ static const int DCH_index[KeyWord_INDEX_SIZE] = {
 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 	-1, -1, -1, -1, -1, DCH_A_D, DCH_B_C, DCH_CC, DCH_DAY, -1,
-	DCH_FX, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
+	DCH_FF1, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
 	DCH_P_M, DCH_Q, DCH_RM, DCH_SSSS, DCH_TZH, DCH_US, -1, DCH_WW, -1, DCH_Y_YYY,
 	-1, -1, -1, -1, -1, -1, -1, DCH_a_d, DCH_b_c, DCH_cc,
-	DCH_day, -1, DCH_fx, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
+	DCH_day, -1, DCH_ff1, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
 	-1, -1, DCH_p_m, DCH_q, DCH_rm, DCH_ssss, DCH_tz, DCH_us, -1, DCH_ww,
 	-1, DCH_y_yyy, -1, -1, -1, -1
 
@@ -962,6 +988,10 @@ typedef struct NUMProc
 			   *L_currency_symbol;
 } NUMProc;
 
+/* Return flags for DCH_from_char() */
+#define DCH_DATED	0x01
+#define DCH_TIMED	0x02
+#define DCH_ZONED	0x04
 
 /* ----------
  * Functions
@@ -977,7 +1007,8 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 
 static void DCH_to_char(FormatNode *node, bool is_interval,
 						TmToChar *in, char *out, Oid collid);
-static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out);
+static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
+						  bool strict);
 
 #ifdef DEBUG_TO_FROM_CHAR
 static void dump_index(const KeyWord *k, const int *index);
@@ -994,8 +1025,8 @@ static int	from_char_parse_int_len(int *dest, char **src, const int len, FormatN
 static int	from_char_parse_int(int *dest, char **src, FormatNode *node);
 static int	seq_search(char *name, const char *const *array, int type, int max, int *len);
 static int	from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
-static void do_to_timestamp(text *date_txt, text *fmt,
-							struct pg_tm *tm, fsec_t *fsec);
+static void do_to_timestamp(text *date_txt, text *fmt, bool strict,
+							struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags);
 static char *fill_str(char *str, int c, int max);
 static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
 static char *int_to_roman(int number);
@@ -2517,18 +2548,32 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 					str_numth(s, s, S_TH_TYPE(n->suffix));
 				s += strlen(s);
 				break;
-			case DCH_MS:		/* millisecond */
-				sprintf(s, "%03d", (int) (in->fsec / INT64CONST(1000)));
-				if (S_THth(n->suffix))
-					str_numth(s, s, S_TH_TYPE(n->suffix));
+#define DCH_to_char_fsec(frac_fmt, frac_val) \
+				sprintf(s, frac_fmt, (int) (frac_val)); \
+				if (S_THth(n->suffix)) \
+					str_numth(s, s, S_TH_TYPE(n->suffix)); \
 				s += strlen(s);
+			case DCH_FF1:		/* decisecond */
+				DCH_to_char_fsec("%01d", in->fsec / 100000);
+				break;
+			case DCH_FF2:		/* centisecond */
+				DCH_to_char_fsec("%02d", in->fsec / 10000);
+				break;
+			case DCH_FF3:
+			case DCH_MS:		/* millisecond */
+				DCH_to_char_fsec("%03d", in->fsec / 1000);
+				break;
+			case DCH_FF4:
+				DCH_to_char_fsec("%04d", in->fsec / 100);
 				break;
+			case DCH_FF5:
+				DCH_to_char_fsec("%05d", in->fsec / 10);
+				break;
+			case DCH_FF6:
 			case DCH_US:		/* microsecond */
-				sprintf(s, "%06d", (int) in->fsec);
-				if (S_THth(n->suffix))
-					str_numth(s, s, S_TH_TYPE(n->suffix));
-				s += strlen(s);
+				DCH_to_char_fsec("%06d", in->fsec);
 				break;
+#undef DCH_to_char_fsec
 			case DCH_SSSS:
 				sprintf(s, "%d", tm->tm_hour * SECS_PER_HOUR +
 						tm->tm_min * SECS_PER_MINUTE +
@@ -3010,13 +3055,15 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 /* ----------
  * Process a string as denoted by a list of FormatNodes.
  * The TmFromChar struct pointed to by 'out' is populated with the results.
+ * 'strict' enables error reporting on unmatched trailing characters in input or
+ * format strings patterns.
  *
  * Note: we currently don't have any to_interval() function, so there
  * is no need here for INVALID_FOR_INTERVAL checks.
  * ----------
  */
 static void
-DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
+DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 {
 	FormatNode *n;
 	char	   *s;
@@ -3153,8 +3200,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 
 				SKIP_THth(s, n->suffix);
 				break;
+			case DCH_FF1:
+			case DCH_FF2:
+			case DCH_FF3:
+			case DCH_FF4:
+			case DCH_FF5:
+			case DCH_FF6:
+				out->ff = n->key->id - DCH_FF1 + 1;
+				/* fall through */
 			case DCH_US:		/* microsecond */
-				len = from_char_parse_int_len(&out->us, &s, 6, n);
+				len = from_char_parse_int_len(&out->us, &s,
+											  n->key->id == DCH_US ? 6 :
+											  out->ff, n);
 
 				out->us *= len == 1 ? 100000 :
 					len == 2 ? 10000 :
@@ -3380,6 +3437,23 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 			}
 		}
 	}
+
+	if (strict)
+	{
+		if (n->type != NODE_TYPE_END)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+					 errmsg("input string is too short for datetime format")));
+
+		while (*s != '\0' && isspace((unsigned char) *s))
+			s++;
+
+		if (*s != '\0')
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+					 errmsg("trailing characters remain in input string after "
+							"datetime format")));
+	}
 }
 
 /*
@@ -3400,6 +3474,109 @@ DCH_prevent_counter_overflow(void)
 	}
 }
 
+/* Get mask of date/time/zone components present in format nodes. */
+static int
+DCH_datetime_type(FormatNode *node)
+{
+	FormatNode *n;
+	int			flags = 0;
+
+	for (n = node; n->type != NODE_TYPE_END; n++)
+	{
+		if (n->type != NODE_TYPE_ACTION)
+			continue;
+
+		switch (n->key->id)
+		{
+			case DCH_FX:
+				break;
+			case DCH_A_M:
+			case DCH_P_M:
+			case DCH_a_m:
+			case DCH_p_m:
+			case DCH_AM:
+			case DCH_PM:
+			case DCH_am:
+			case DCH_pm:
+			case DCH_HH:
+			case DCH_HH12:
+			case DCH_HH24:
+			case DCH_MI:
+			case DCH_SS:
+			case DCH_MS:		/* millisecond */
+			case DCH_US:		/* microsecond */
+			case DCH_FF1:
+			case DCH_FF2:
+			case DCH_FF3:
+			case DCH_FF4:
+			case DCH_FF5:
+			case DCH_FF6:
+			case DCH_SSSS:
+				flags |= DCH_TIMED;
+				break;
+			case DCH_tz:
+			case DCH_TZ:
+			case DCH_OF:
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("formatting field \"%s\" is only supported in to_char",
+								n->key->name)));
+				flags |= DCH_ZONED;
+				break;
+			case DCH_TZH:
+			case DCH_TZM:
+				flags |= DCH_ZONED;
+				break;
+			case DCH_A_D:
+			case DCH_B_C:
+			case DCH_a_d:
+			case DCH_b_c:
+			case DCH_AD:
+			case DCH_BC:
+			case DCH_ad:
+			case DCH_bc:
+			case DCH_MONTH:
+			case DCH_Month:
+			case DCH_month:
+			case DCH_MON:
+			case DCH_Mon:
+			case DCH_mon:
+			case DCH_MM:
+			case DCH_DAY:
+			case DCH_Day:
+			case DCH_day:
+			case DCH_DY:
+			case DCH_Dy:
+			case DCH_dy:
+			case DCH_DDD:
+			case DCH_IDDD:
+			case DCH_DD:
+			case DCH_D:
+			case DCH_ID:
+			case DCH_WW:
+			case DCH_Q:
+			case DCH_CC:
+			case DCH_Y_YYY:
+			case DCH_YYYY:
+			case DCH_IYYY:
+			case DCH_YYY:
+			case DCH_IYY:
+			case DCH_YY:
+			case DCH_IY:
+			case DCH_Y:
+			case DCH_I:
+			case DCH_RM:
+			case DCH_rm:
+			case DCH_W:
+			case DCH_J:
+				flags |= DCH_DATED;
+				break;
+		}
+	}
+
+	return flags;
+}
+
 /* select a DCHCacheEntry to hold the given format picture */
 static DCHCacheEntry *
 DCH_cache_getnew(const char *str)
@@ -3688,8 +3865,9 @@ to_timestamp(PG_FUNCTION_ARGS)
 	int			tz;
 	struct pg_tm tm;
 	fsec_t		fsec;
+	int			fprec;
 
-	do_to_timestamp(date_txt, fmt, &tm, &fsec);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL);
 
 	/* Use the specified time zone, if any. */
 	if (tm.tm_zone)
@@ -3707,6 +3885,10 @@ to_timestamp(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 				 errmsg("timestamp out of range")));
 
+	/* Use the specified fractional precision, if any. */
+	if (fprec)
+		AdjustTimestampForTypmod(&result, fprec);
+
 	PG_RETURN_TIMESTAMP(result);
 }
 
@@ -3724,7 +3906,7 @@ to_date(PG_FUNCTION_ARGS)
 	struct pg_tm tm;
 	fsec_t		fsec;
 
-	do_to_timestamp(date_txt, fmt, &tm, &fsec);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL);
 
 	/* Prevent overflow in Julian-day routines */
 	if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3745,11 +3927,171 @@ to_date(PG_FUNCTION_ARGS)
 	PG_RETURN_DATEADT(result);
 }
 
+/*
+ * Make datetime type from 'date_txt' which is formated at argument 'fmt'.
+ * Actual datatype (returned in 'typid', 'typmod') is determined by
+ * presence of date/time/zone components in the format string.
+ *
+ * Default time-zone for tz types is specified with 'tzname'.  If 'tzname' is
+ * NULL and the input string does not contain zone components then "missing tz"
+ * error is thrown.
+ */
+Datum
+parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
+			   int32 *typmod, int *tz)
+{
+	struct pg_tm tm;
+	fsec_t		fsec;
+	int			fprec = 0;
+	int			flags;
+
+	do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags);
+
+	*typmod = fprec ? fprec : -1;	/* fractional part precision */
+
+	if (flags & DCH_DATED)
+	{
+		if (flags & DCH_TIMED)
+		{
+			if (flags & DCH_ZONED)
+			{
+				TimestampTz result;
+
+				if (tm.tm_zone)
+				{
+					int			dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
+
+					if (dterr)
+						DateTimeParseError(dterr, text_to_cstring(date_txt), "timestamptz");
+				}
+				else
+				{
+					if (*tz == PG_INT32_MIN)
+						ereport(ERROR,
+								(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								 errmsg("missing time-zone in input string for type timestamptz")));
+				}
+
+				if (tm2timestamp(&tm, fsec, tz, &result) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("timestamptz out of range")));
+
+				AdjustTimestampForTypmod(&result, *typmod);
+
+				*typid = TIMESTAMPTZOID;
+				return TimestampTzGetDatum(result);
+			}
+			else
+			{
+				Timestamp	result;
+
+				if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("timestamp out of range")));
+
+				AdjustTimestampForTypmod(&result, *typmod);
+
+				*typid = TIMESTAMPOID;
+				return TimestampGetDatum(result);
+			}
+		}
+		else
+		{
+			if (flags & DCH_ZONED)
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+						 errmsg("datetime format is zoned but not timed")));
+			}
+			else
+			{
+				DateADT		result;
+
+				/* Prevent overflow in Julian-day routines */
+				if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("date out of range: \"%s\"",
+									text_to_cstring(date_txt))));
+
+				result = 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(result))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("date out of range: \"%s\"",
+									text_to_cstring(date_txt))));
+
+				*typid = DATEOID;
+				return DateADTGetDatum(result);
+			}
+		}
+	}
+	else if (flags & DCH_TIMED)
+	{
+		if (flags & DCH_ZONED)
+		{
+			TimeTzADT  *result = palloc(sizeof(TimeTzADT));
+
+			if (tm.tm_zone)
+			{
+				int			dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
+
+				if (dterr)
+					DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz");
+			}
+			else
+			{
+				if (*tz == PG_INT32_MIN)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							 errmsg("missing time-zone in input string for type timetz")));
+			}
+
+			if (tm2timetz(&tm, fsec, *tz, result) != 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("timetz out of range")));
+
+			AdjustTimeForTypmod(&result->time, *typmod);
+
+			*typid = TIMETZOID;
+			return TimeTzADTPGetDatum(result);
+		}
+		else
+		{
+			TimeADT		result;
+
+			if (tm2time(&tm, fsec, &result) != 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("time out of range")));
+
+			AdjustTimeForTypmod(&result, *typmod);
+
+			*typid = TIMEOID;
+			return TimeADTGetDatum(result);
+		}
+	}
+	else
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+				 errmsg("datetime format is not dated and not timed")));
+	}
+
+	return (Datum) 0;
+}
+
 /*
  * do_to_timestamp: shared code for to_timestamp and to_date
  *
  * Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm
- * and fractional seconds.
+ * and fractional seconds and fractional precision.
  *
  * We parse 'fmt' into a list of FormatNodes, which is then passed to
  * DCH_from_char to populate a TmFromChar with the parsed contents of
@@ -3757,10 +4099,15 @@ to_date(PG_FUNCTION_ARGS)
  *
  * The TmFromChar is then analysed and converted into the final results in
  * struct 'tm' and 'fsec'.
+ *
+ * Bit mask of date/time/zone components found in 'fmt' is returned in 'flags'.
+ *
+ * 'strict' enables error reporting on unmatched trailing characters in input or
+ * format strings patterns.
  */
 static void
-do_to_timestamp(text *date_txt, text *fmt,
-				struct pg_tm *tm, fsec_t *fsec)
+do_to_timestamp(text *date_txt, text *fmt, bool strict,
+				struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags)
 {
 	FormatNode *format;
 	TmFromChar	tmfc;
@@ -3813,9 +4160,13 @@ do_to_timestamp(text *date_txt, text *fmt,
 		/* dump_index(DCH_keywords, DCH_index); */
 #endif
 
-		DCH_from_char(format, date_str, &tmfc);
+		DCH_from_char(format, date_str, &tmfc, strict);
 
 		pfree(fmt_str);
+
+		if (flags)
+			*flags = DCH_datetime_type(format);
+
 		if (!incache)
 			pfree(format);
 	}
@@ -3997,6 +4348,8 @@ do_to_timestamp(text *date_txt, text *fmt,
 		*fsec += tmfc.ms * 1000;
 	if (tmfc.us)
 		*fsec += tmfc.us;
+	if (fprec)
+		*fprec = tmfc.ff;		/* fractional precision, if specified */
 
 	/* Range-check date fields according to bit mask computed above */
 	if (fmask != 0)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 5861ffbbc97..2931bd58c0d 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -70,7 +70,6 @@ typedef struct
 
 static TimeOffset time2t(const int hour, const int min, const int sec, const fsec_t fsec);
 static Timestamp dt2local(Timestamp dt, int timezone);
-static void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
 static void AdjustIntervalForTypmod(Interval *interval, int32 typmod);
 static TimestampTz timestamp2timestamptz(Timestamp timestamp);
 static Timestamp timestamptz2timestamp(TimestampTz timestamp);
@@ -333,7 +332,7 @@ timestamp_scale(PG_FUNCTION_ARGS)
  * AdjustTimestampForTypmod --- round off a timestamp to suit given typmod
  * Works for either timestamp or timestamptz.
  */
-static void
+void
 AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
 {
 	static const int64 TimestampScales[MAX_TIMESTAMP_PRECISION + 1] = {
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index bec129aff1c..bd15bfa5bb0 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -76,5 +76,8 @@ extern TimeTzADT *GetSQLCurrentTime(int32 typmod);
 extern TimeADT GetSQLLocalTime(int32 typmod);
 extern int	time2tm(TimeADT time, struct pg_tm *tm, fsec_t *fsec);
 extern int	timetz2tm(TimeTzADT *time, struct pg_tm *tm, fsec_t *fsec, int *tzp);
+extern int	tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
+extern int	tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
+extern void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
 
 #endif							/* DATE_H */
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index b8a199cdded..0cafdd26538 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -336,4 +336,6 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
 												   int n);
 extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
 
+extern void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
+
 #endif							/* DATETIME_H */
diff --git a/src/include/utils/formatting.h b/src/include/utils/formatting.h
index 5b275dc9850..a390302e89c 100644
--- a/src/include/utils/formatting.h
+++ b/src/include/utils/formatting.h
@@ -28,4 +28,7 @@ extern char *asc_tolower(const char *buff, size_t nbytes);
 extern char *asc_toupper(const char *buff, size_t nbytes);
 extern char *asc_initcap(const char *buff, size_t nbytes);
 
+extern Datum parse_datetime(text *date_txt, text *fmt, bool strict,
+							Oid *typid, int32 *typmod, int *tz);
+
 #endif
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index b2b45773339..74ecb7c10e6 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -2786,6 +2786,85 @@ SELECT to_timestamp('2011-12-18 11:38 20',     'YYYY-MM-DD HH12:MI TZM');
  Sun Dec 18 03:18:00 2011 PST
 (1 row)
 
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |         to_timestamp         
+---+------------------------------
+ 1 | Fri Nov 02 12:34:56 2018 PDT
+ 2 | Fri Nov 02 12:34:56 2018 PDT
+ 3 | Fri Nov 02 12:34:56 2018 PDT
+ 4 | Fri Nov 02 12:34:56 2018 PDT
+ 5 | Fri Nov 02 12:34:56 2018 PDT
+ 6 | Fri Nov 02 12:34:56 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |          to_timestamp          
+---+--------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.1 2018 PDT
+ 3 | Fri Nov 02 12:34:56.1 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |          to_timestamp           
+---+---------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.12 2018 PDT
+ 4 | Fri Nov 02 12:34:56.12 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |           to_timestamp           
+---+----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.123 2018 PDT
+ 5 | Fri Nov 02 12:34:56.123 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |           to_timestamp            
+---+-----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1234 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |            to_timestamp            
+---+------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12345 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12345 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |            to_timestamp             
+---+-------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12346 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123456 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ERROR:  date/time field value out of range: "2018-11-02 12:34:56.123456789"
 --
 -- Check handling of multiple spaces in format and/or input
 --
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index 715680e3302..f772b07d5a4 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -1584,6 +1584,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
             | 2001 1 1 1 1 1 1
 (65 rows)
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamp),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+ to_char_12 |                              to_char                               
+------------+--------------------------------------------------------------------
+            | 0 00 000 0000 00000 000000  0 00 000 0000 00000 000000  000 000000
+            | 7 78 780 7800 78000 780000  7 78 780 7800 78000 780000  780 780000
+            | 7 78 789 7890 78901 789010  7 78 789 7890 78901 789010  789 789010
+            | 7 78 789 7890 78901 789012  7 78 789 7890 78901 789012  789 789012
+(4 rows)
+
 -- timestamp numeric fields constructor
 SELECT make_timestamp(2014,12,28,6,30,45.887);
         make_timestamp        
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 5551fa6610e..2d6a71ca64b 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -1704,6 +1704,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
             | 2001 1 1 1 1 1 1
 (66 rows)
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamptz),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+ to_char_12 |                              to_char                               
+------------+--------------------------------------------------------------------
+            | 0 00 000 0000 00000 000000  0 00 000 0000 00000 000000  000 000000
+            | 7 78 780 7800 78000 780000  7 78 780 7800 78000 780000  780 780000
+            | 7 78 789 7890 78901 789010  7 78 789 7890 78901 789010  789 789010
+            | 7 78 789 7890 78901 789012  7 78 789 7890 78901 789012  789 789012
+(4 rows)
+
 -- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
 SET timezone = '00:00';
 SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index e356dd563ee..3c8580397ac 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -402,6 +402,15 @@ SELECT to_timestamp('2011-12-18 11:38 +05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
 SELECT to_timestamp('2011-12-18 11:38 -05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
 SELECT to_timestamp('2011-12-18 11:38 20',     'YYYY-MM-DD HH12:MI TZM');
 
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+
 --
 -- Check handling of multiple spaces in format and/or input
 --
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index 031b22bc3c1..329987f7eaa 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -224,5 +224,13 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
 SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
    FROM TIMESTAMP_TBL;
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamp),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+
 -- timestamp numeric fields constructor
 SELECT make_timestamp(2014,12,28,6,30,45.887);
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index 28c76d6b72c..f5fee639a01 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -248,6 +248,14 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
 SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
    FROM TIMESTAMPTZ_TBL;
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamptz),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+
 -- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
 SET timezone = '00:00';
 SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
0004-implement-jsonpath-datetime-2.patchapplication/octet-stream; name=0004-implement-jsonpath-datetime-2.patchDownload
commit 9048bd9b0deed29e82a14f94e5a5e3ba4b8c430e
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Mon May 27 10:33:09 2019 +0300

    Implement jsonpath .datetime() method
    
    This commit implements jsonpath .datetime() method as it's specified in
    SQL/JSON standard.  There are no-argument and single-argument versions of
    this method.  No-argument version selects first of ISO datetime formats
    matching input string.  Single-argument version accepts template string as
    its argument.
    
    Additionally to .datetime() method itself this commit also implements
    comparison ability of resulting date and time values.  There is some difficulty
    because exising jsonb_path_*() functions are immutable, while comparison of
    timezoned and non-timezoned types involves current timezone.  At first, current
    timezone could be changes in session.  Moreover, timezones themselves are not
    immutable and could be updated.  This is why we let existing immutable functions
    throw errors on such non-immutable comparison.  In the same time this commit
    provides jsonb_path_*_tz() functions which are stable and support operations
    involving timezones.

diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ea4c85e3959..423ae4fdc8a 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1287,6 +1287,46 @@ LANGUAGE INTERNAL
 STRICT IMMUTABLE PARALLEL SAFE
 AS 'jsonb_path_query_first';
 
+CREATE OR REPLACE FUNCTION
+  jsonb_path_exists_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                    silent boolean DEFAULT false)
+RETURNS boolean
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_exists_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_match_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                   silent boolean DEFAULT false)
+RETURNS boolean
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_match_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_query_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                   silent boolean DEFAULT false)
+RETURNS SETOF jsonb
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_query_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_query_array_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                         silent boolean DEFAULT false)
+RETURNS jsonb
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_query_array_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_query_first_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                         silent boolean DEFAULT false)
+RETURNS jsonb
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_query_first_tz';
+
 --
 -- The default permissions for functions mean that anyone can execute them.
 -- A number of functions shouldn't be executable by just anyone, but rather
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 87ae60e490f..65c047acc86 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -337,12 +337,14 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
 		case jpiPlus:
 		case jpiMinus:
 		case jpiExists:
+		case jpiDatetime:
 			{
 				int32		arg = reserveSpaceForItemPointer(buf);
 
-				chld = flattenJsonPathParseItem(buf, item->value.arg,
-												nestingLevel + argNestingLevel,
-												insideArraySubscript);
+				chld = !item->value.arg ? pos :
+					flattenJsonPathParseItem(buf, item->value.arg,
+											 nestingLevel + argNestingLevel,
+											 insideArraySubscript);
 				*(int32 *) (buf->data + arg) = chld - pos;
 			}
 			break;
@@ -692,6 +694,15 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
 		case jpiDouble:
 			appendBinaryStringInfo(buf, ".double()", 9);
 			break;
+		case jpiDatetime:
+			appendBinaryStringInfo(buf, ".datetime(", 10);
+			if (v->content.arg)
+			{
+				jspGetArg(v, &elem);
+				printJsonPathItem(buf, &elem, false, false);
+			}
+			appendStringInfoChar(buf, ')');
+			break;
 		case jpiKeyValue:
 			appendBinaryStringInfo(buf, ".keyvalue()", 11);
 			break;
@@ -754,6 +765,8 @@ jspOperationName(JsonPathItemType type)
 			return "floor";
 		case jpiCeiling:
 			return "ceiling";
+		case jpiDatetime:
+			return "datetime";
 		default:
 			elog(ERROR, "unrecognized jsonpath item type: %d", type);
 			return NULL;
@@ -889,6 +902,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
 		case jpiPlus:
 		case jpiMinus:
 		case jpiFilter:
+		case jpiDatetime:
 			read_int32(v->content.arg, base, pos);
 			break;
 		case jpiIndexArray:
@@ -913,7 +927,8 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a)
 		   v->type == jpiIsUnknown ||
 		   v->type == jpiExists ||
 		   v->type == jpiPlus ||
-		   v->type == jpiMinus);
+		   v->type == jpiMinus ||
+		   v->type == jpiDatetime);
 
 	jspInitByBuffer(a, v->base, v->content.arg);
 }
@@ -961,6 +976,7 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
 			   v->type == jpiFloor ||
 			   v->type == jpiCeiling ||
 			   v->type == jpiDouble ||
+			   v->type == jpiDatetime ||
 			   v->type == jpiKeyValue ||
 			   v->type == jpiStartsWith);
 
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index 6bf4dcaec33..196f1901b4e 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -66,6 +66,7 @@
 #include "miscadmin.h"
 #include "regex/regex.h"
 #include "utils/builtins.h"
+#include "utils/datetime.h"
 #include "utils/datum.h"
 #include "utils/formatting.h"
 #include "utils/float.h"
@@ -107,6 +108,7 @@ typedef struct JsonPathExecContext
 										 * ignored */
 	bool		throwErrors;	/* with "false" all suppressible errors are
 								 * suppressed */
+	bool		useTz;
 } JsonPathExecContext;
 
 /* Context for LIKE_REGEX execution. */
@@ -172,7 +174,8 @@ typedef JsonPathBool (*JsonPathPredicateCallback) (JsonPathItem *jsp,
 typedef Numeric (*BinaryArithmFunc) (Numeric num1, Numeric num2, bool *error);
 
 static JsonPathExecResult executeJsonPath(JsonPath *path, Jsonb *vars,
-										  Jsonb *json, bool throwErrors, JsonValueList *result);
+										  Jsonb *json, bool throwErrors,
+										  JsonValueList *result, bool useTz);
 static JsonPathExecResult executeItem(JsonPathExecContext *cxt,
 									  JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
 static JsonPathExecResult executeItemOptUnwrapTarget(JsonPathExecContext *cxt,
@@ -215,6 +218,8 @@ static JsonPathBool executeLikeRegex(JsonPathItem *jsp, JsonbValue *str,
 static JsonPathExecResult executeNumericItemMethod(JsonPathExecContext *cxt,
 												   JsonPathItem *jsp, JsonbValue *jb, bool unwrap, PGFunction func,
 												   JsonValueList *found);
+static JsonPathExecResult executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
+												JsonbValue *jb, JsonValueList *found);
 static JsonPathExecResult executeKeyValueMethod(JsonPathExecContext *cxt,
 												JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
 static JsonPathExecResult appendBoolResult(JsonPathExecContext *cxt,
@@ -226,7 +231,8 @@ static void getJsonPathVariable(JsonPathExecContext *cxt,
 static int	JsonbArraySize(JsonbValue *jb);
 static JsonPathBool executeComparison(JsonPathItem *cmp, JsonbValue *lv,
 									  JsonbValue *rv, void *p);
-static JsonPathBool compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2);
+static JsonPathBool compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2,
+								 bool useTz);
 static int	compareNumeric(Numeric a, Numeric b);
 static JsonbValue *copyJsonbValue(JsonbValue *src);
 static JsonPathExecResult getArrayIndex(JsonPathExecContext *cxt,
@@ -247,6 +253,8 @@ static JsonbValue *JsonbInitBinary(JsonbValue *jbv, Jsonb *jb);
 static int	JsonbType(JsonbValue *jb);
 static JsonbValue *getScalar(JsonbValue *scalar, enum jbvType type);
 static JsonbValue *wrapItemsInArray(const JsonValueList *items);
+static int	compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
+							bool useTz, bool *have_error);
 
 /****************** User interface to JsonPath executor ********************/
 
@@ -262,8 +270,8 @@ static JsonbValue *wrapItemsInArray(const JsonValueList *items);
  *		SQL/JSON.  Regarding jsonb_path_match(), this function doesn't have
  *		an analogy in SQL/JSON, so we define its behavior on our own.
  */
-Datum
-jsonb_path_exists(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_exists_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -277,7 +285,7 @@ jsonb_path_exists(PG_FUNCTION_ARGS)
 		silent = PG_GETARG_BOOL(3);
 	}
 
-	res = executeJsonPath(jp, vars, jb, !silent, NULL);
+	res = executeJsonPath(jp, vars, jb, !silent, NULL, tz);
 
 	PG_FREE_IF_COPY(jb, 0);
 	PG_FREE_IF_COPY(jp, 1);
@@ -288,6 +296,18 @@ jsonb_path_exists(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(res == jperOk);
 }
 
+Datum
+jsonb_path_exists(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_exists_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_exists_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_exists_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_exists_opr
  *		Implementation of operator "jsonb @? jsonpath" (2-argument version of
@@ -297,7 +317,7 @@ Datum
 jsonb_path_exists_opr(PG_FUNCTION_ARGS)
 {
 	/* just call the other one -- it can handle both cases */
-	return jsonb_path_exists(fcinfo);
+	return jsonb_path_exists_internal(fcinfo, false);
 }
 
 /*
@@ -305,8 +325,8 @@ jsonb_path_exists_opr(PG_FUNCTION_ARGS)
  *		Returns jsonpath predicate result item for the specified jsonb value.
  *		See jsonb_path_exists() comment for details regarding error handling.
  */
-Datum
-jsonb_path_match(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_match_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -320,7 +340,7 @@ jsonb_path_match(PG_FUNCTION_ARGS)
 		silent = PG_GETARG_BOOL(3);
 	}
 
-	(void) executeJsonPath(jp, vars, jb, !silent, &found);
+	(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 	PG_FREE_IF_COPY(jb, 0);
 	PG_FREE_IF_COPY(jp, 1);
@@ -344,6 +364,18 @@ jsonb_path_match(PG_FUNCTION_ARGS)
 	PG_RETURN_NULL();
 }
 
+Datum
+jsonb_path_match(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_match_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_match_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_match_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_match_opr
  *		Implementation of operator "jsonb @@ jsonpath" (2-argument version of
@@ -353,7 +385,7 @@ Datum
 jsonb_path_match_opr(PG_FUNCTION_ARGS)
 {
 	/* just call the other one -- it can handle both cases */
-	return jsonb_path_match(fcinfo);
+	return jsonb_path_match_internal(fcinfo, false);
 }
 
 /*
@@ -361,8 +393,8 @@ jsonb_path_match_opr(PG_FUNCTION_ARGS)
  *		Executes jsonpath for given jsonb document and returns result as
  *		rowset.
  */
-Datum
-jsonb_path_query(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_query_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	FuncCallContext *funcctx;
 	List	   *found;
@@ -386,7 +418,7 @@ jsonb_path_query(PG_FUNCTION_ARGS)
 		vars = PG_GETARG_JSONB_P_COPY(2);
 		silent = PG_GETARG_BOOL(3);
 
-		(void) executeJsonPath(jp, vars, jb, !silent, &found);
+		(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 		funcctx->user_fctx = JsonValueListGetList(&found);
 
@@ -407,13 +439,25 @@ jsonb_path_query(PG_FUNCTION_ARGS)
 	SRF_RETURN_NEXT(funcctx, JsonbPGetDatum(JsonbValueToJsonb(v)));
 }
 
+Datum
+jsonb_path_query(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_query_array
  *		Executes jsonpath for given jsonb document and returns result as
  *		jsonb array.
  */
-Datum
-jsonb_path_query_array(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_query_array_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -421,18 +465,30 @@ jsonb_path_query_array(PG_FUNCTION_ARGS)
 	Jsonb	   *vars = PG_GETARG_JSONB_P(2);
 	bool		silent = PG_GETARG_BOOL(3);
 
-	(void) executeJsonPath(jp, vars, jb, !silent, &found);
+	(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 	PG_RETURN_JSONB_P(JsonbValueToJsonb(wrapItemsInArray(&found)));
 }
 
+Datum
+jsonb_path_query_array(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_array_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_array_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_array_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_query_first
  *		Executes jsonpath for given jsonb document and returns first result
  *		item.  If there are no items, NULL returned.
  */
-Datum
-jsonb_path_query_first(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_query_first_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -440,7 +496,7 @@ jsonb_path_query_first(PG_FUNCTION_ARGS)
 	Jsonb	   *vars = PG_GETARG_JSONB_P(2);
 	bool		silent = PG_GETARG_BOOL(3);
 
-	(void) executeJsonPath(jp, vars, jb, !silent, &found);
+	(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 	if (JsonValueListLength(&found) >= 1)
 		PG_RETURN_JSONB_P(JsonbValueToJsonb(JsonValueListHead(&found)));
@@ -448,6 +504,18 @@ jsonb_path_query_first(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 }
 
+Datum
+jsonb_path_query_first(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_first_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_first_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_first_internal(fcinfo, true);
+}
+
 /********************Execute functions for JsonPath**************************/
 
 /*
@@ -471,7 +539,7 @@ jsonb_path_query_first(PG_FUNCTION_ARGS)
  */
 static JsonPathExecResult
 executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors,
-				JsonValueList *result)
+				JsonValueList *result, bool useTz)
 {
 	JsonPathExecContext cxt;
 	JsonPathExecResult res;
@@ -501,6 +569,7 @@ executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors,
 	cxt.lastGeneratedObjectId = vars ? 2 : 1;
 	cxt.innermostArraySize = -1;
 	cxt.throwErrors = throwErrors;
+	cxt.useTz = useTz;
 
 	if (jspStrictAbsenseOfErrors(&cxt) && !result)
 	{
@@ -1029,6 +1098,12 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
 			}
 			break;
 
+		case jpiDatetime:
+			if (unwrap && JsonbType(jb) == jbvArray)
+				return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
+
+			return executeDateTimeMethod(cxt, jsp, jb, found);
+
 		case jpiKeyValue:
 			if (unwrap && JsonbType(jb) == jbvArray)
 				return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
@@ -1215,7 +1290,7 @@ executeBoolItem(JsonPathExecContext *cxt, JsonPathItem *jsp,
 			jspGetLeftArg(jsp, &larg);
 			jspGetRightArg(jsp, &rarg);
 			return executePredicate(cxt, jsp, &larg, &rarg, jb, true,
-									executeComparison, NULL);
+									executeComparison, cxt);
 
 		case jpiStartsWith:		/* 'whole STARTS WITH initial' */
 			jspGetLeftArg(jsp, &larg);	/* 'whole' */
@@ -1718,6 +1793,135 @@ executeNumericItemMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
 	return executeNextItem(cxt, jsp, &next, jb, found, false);
 }
 
+/*
+ * Implementation of .datetime() method.
+ *
+ * Parses string as datetime value.  Particular type is determined runtime.
+ * When argument is provided then it's used as template string.  When no
+ * argument is supplied, first fitting ISO format is selected.
+ */
+static JsonPathExecResult
+executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
+					  JsonbValue *jb, JsonValueList *found)
+{
+	JsonbValue	jbvbuf;
+	Datum		value;
+	text	   *datetime;
+	Oid			typid;
+	int32		typmod = -1;
+	int			tz = 0;
+	bool		hasNext;
+	JsonPathExecResult res = jperNotFound;
+	JsonPathItem elem;
+
+	if (!(jb = getScalar(jb, jbvString)))
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+							  errmsg("invalid argument for SQL/JSON datetime function"),
+							  errdetail("jsonpath item method .%s() is applied to not a string",
+										jspOperationName(jsp->type)))));
+
+	datetime = cstring_to_text_with_len(jb->val.string.val,
+										jb->val.string.len);
+
+	if (jsp->content.arg)
+	{
+		text	   *template;
+		char	   *template_str;
+		int			template_len;
+		bool		have_error = false;
+
+		jspGetArg(jsp, &elem);
+
+		if (elem.type != jpiString)
+			elog(ERROR, "invalid jsonpath item type for .datetime() argument");
+
+		template_str = jspGetString(&elem, &template_len);
+
+		template = cstring_to_text_with_len(template_str,
+											template_len);
+
+		value = parse_datetime(datetime, template, true,
+							   &typid, &typmod, &tz,
+							   jspThrowErrors(cxt) ? NULL : &have_error);
+
+		if (have_error)
+			res = jperError;
+		else
+			res = jperOk;
+	}
+	else
+	{
+		/* Try to recognize one of ISO formats. */
+		static const char *fmt_str[] =
+		{
+			"yyyy-mm-dd HH24:MI:SS TZH:TZM",
+			"yyyy-mm-dd HH24:MI:SS TZH",
+			"yyyy-mm-dd HH24:MI:SS",
+			"yyyy-mm-dd",
+			"HH24:MI:SS TZH:TZM",
+			"HH24:MI:SS TZH",
+			"HH24:MI:SS"
+		};
+
+		/* cache for format texts */
+		static text *fmt_txt[lengthof(fmt_str)] = {0};
+		int			i;
+
+		/* loop until datetime format fits */
+		for (i = 0; i < lengthof(fmt_str); i++)
+		{
+			bool		have_error = false;
+
+			if (!fmt_txt[i])
+			{
+				MemoryContext oldcxt =
+				MemoryContextSwitchTo(TopMemoryContext);
+
+				fmt_txt[i] = cstring_to_text(fmt_str[i]);
+				MemoryContextSwitchTo(oldcxt);
+			}
+
+			value = parse_datetime(datetime, fmt_txt[i], true,
+								   &typid, &typmod, &tz,
+								   &have_error);
+
+			if (!have_error)
+			{
+				res = jperOk;
+				break;
+			}
+		}
+
+		if (res == jperNotFound)
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+								  errmsg("invalid argument for SQL/JSON datetime function"),
+								  errdetail("unrecognized datetime format"),
+								  errhint("use datetime template argument for explicit format specification"))));
+	}
+
+	pfree(datetime);
+
+	if (jperIsError(res))
+		return res;
+
+	hasNext = jspGetNext(jsp, &elem);
+
+	if (!hasNext && !found)
+		return res;
+
+	jb = hasNext ? &jbvbuf : palloc(sizeof(*jb));
+
+	jb->type = jbvDatetime;
+	jb->val.datetime.value = value;
+	jb->val.datetime.typid = typid;
+	jb->val.datetime.typmod = typmod;
+	jb->val.datetime.tz = tz;
+
+	return executeNextItem(cxt, jsp, &elem, jb, found, hasNext);
+}
+
 /*
  * Implementation of .keyvalue() method.
  *
@@ -1978,14 +2182,16 @@ JsonbArraySize(JsonbValue *jb)
 static JsonPathBool
 executeComparison(JsonPathItem *cmp, JsonbValue *lv, JsonbValue *rv, void *p)
 {
-	return compareItems(cmp->type, lv, rv);
+	JsonPathExecContext *cxt = (JsonPathExecContext *) p;
+
+	return compareItems(cmp->type, lv, rv, cxt->useTz);
 }
 
 /*
  * Compare two SQL/JSON items using comparison operation 'op'.
  */
 static JsonPathBool
-compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2)
+compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2, bool useTz)
 {
 	int			cmp;
 	bool		res;
@@ -2027,6 +2233,21 @@ compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2)
 							 jb2->val.string.val, jb2->val.string.len,
 							 DEFAULT_COLLATION_OID);
 			break;
+		case jbvDatetime:
+			{
+				bool		have_error = false;
+
+				cmp = compareDatetime(jb1->val.datetime.value,
+									  jb1->val.datetime.typid,
+									  jb2->val.datetime.value,
+									  jb2->val.datetime.typid,
+									  useTz,
+									  &have_error);
+
+				if (have_error)
+					return jpbUnknown;
+			}
+			break;
 
 		case jbvBinary:
 		case jbvArray:
@@ -2283,3 +2504,205 @@ wrapItemsInArray(const JsonValueList *items)
 
 	return pushJsonbValue(&ps, WJB_END_ARRAY, NULL);
 }
+
+/*
+ * Cross-type comparison of two datetime SQL/JSON items.  If items are
+ * uncomparable, 'error' flag is set.
+ */
+static int
+compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
+				bool useTz, bool *have_error)
+{
+	PGFunction cmpfunc = NULL;
+
+	switch (typid1)
+	{
+		case DATEOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					cmpfunc = date_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					val1 = date2timestamp_opt_error(val1, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("can't convert value from %s to %s without timezone usage",
+										"date", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val1 = date2timestamptz_opt_error(val1, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMEOID:
+			switch (typid2)
+			{
+				case TIMEOID:
+					cmpfunc = time_cmp;
+
+					break;
+
+				case TIMETZOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("can't convert value from %s to %s without timezone usage",
+										"time", "timetz"),
+								 errhint("use *_tz() function for timezone support")));
+					val1 = DirectFunctionCall1(time_timetz, val1);
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case DATEOID:
+				case TIMESTAMPOID:
+				case TIMESTAMPTZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMETZOID:
+			switch (typid2)
+			{
+				case TIMEOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("can't convert value from %s to %s without timezone usage",
+										"time", "timetz"),
+								 errhint("use *_tz() function for timezone support")));
+					val2 = DirectFunctionCall1(time_timetz, val2);
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case TIMETZOID:
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case DATEOID:
+				case TIMESTAMPOID:
+				case TIMESTAMPTZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMESTAMPOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					val2 = date2timestamp_opt_error(val2, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("can't convert value from %s to %s without timezone usage",
+										"timestamp", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val1 = timestamp2timestamptz_opt_error(val1, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMESTAMPTZOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("can't convert value from %s to %s without timezone usage",
+										"date", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val2 = date2timestamptz_opt_error(val2, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("can't convert value from %s to %s without timezone usage",
+										"timestamp", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val2 = timestamp2timestamptz_opt_error(val2, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		default:
+			elog(ERROR, "unrecognized SQL/JSON datetime type oid: %d",
+				 typid1);
+	}
+
+	if (*have_error)
+		return 0;
+
+	if (!cmpfunc)
+		elog(ERROR, "unrecognized SQL/JSON datetime type oid: %d",
+			 typid2);
+
+	*have_error = false;
+
+	return DatumGetInt32(DirectFunctionCall2(cmpfunc, val1, val2));
+}
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
index a0a930ccf0c..912f46c682b 100644
--- a/src/backend/utils/adt/jsonpath_gram.y
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -94,12 +94,14 @@ static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr,
 %token	<str>		LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
 %token	<str>		ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P
 %token	<str>		ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P KEYVALUE_P
+%token	<str>		DATETIME_P
 
 %type	<result>	result
 
 %type	<value>		scalar_value path_primary expr array_accessor
 					any_path accessor_op key predicate delimited_predicate
 					index_elem starts_with_initial expr_or_predicate
+					datetime_template opt_datetime_template
 
 %type	<elems>		accessor_expr
 
@@ -247,9 +249,20 @@ accessor_op:
 	| array_accessor				{ $$ = $1; }
 	| '.' any_path					{ $$ = $2; }
 	| '.' method '(' ')'			{ $$ = makeItemType($2); }
+	| '.' DATETIME_P '(' opt_datetime_template ')'
+									{ $$ = makeItemUnary(jpiDatetime, $4); }
 	| '?' '(' predicate ')'			{ $$ = makeItemUnary(jpiFilter, $3); }
 	;
 
+datetime_template:
+	STRING_P						{ $$ = makeItemString(&$1); }
+	;
+
+opt_datetime_template:
+	datetime_template				{ $$ = $1; }
+	| /* EMPTY */					{ $$ = NULL; }
+	;
+
 key:
 	key_name						{ $$ = makeItemKey(&$1); }
 	;
@@ -272,6 +285,7 @@ key_name:
 	| FLOOR_P
 	| DOUBLE_P
 	| CEILING_P
+	| DATETIME_P
 	| KEYVALUE_P
 	| LAST_P
 	| STARTS_P
diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l
index 2165ffcc254..ced65edb59b 100644
--- a/src/backend/utils/adt/jsonpath_scan.l
+++ b/src/backend/utils/adt/jsonpath_scan.l
@@ -340,6 +340,7 @@ static const JsonPathKeyword keywords[] = {
 	{ 6, false,	STRICT_P,	"strict"},
 	{ 7, false,	CEILING_P,	"ceiling"},
 	{ 7, false,	UNKNOWN_P,	"unknown"},
+	{ 8, false,	DATETIME_P,	"datetime"},
 	{ 8, false,	KEYVALUE_P,	"keyvalue"},
 	{ 10,false, LIKE_REGEX_P, "like_regex"},
 };
diff --git a/src/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index 16f5ca233a9..72876533acf 100644
--- a/src/backend/utils/errcodes.txt
+++ b/src/backend/utils/errcodes.txt
@@ -207,6 +207,7 @@ Section: Class 22 - Data Exception
 2200S    E    ERRCODE_INVALID_XML_COMMENT                                    invalid_xml_comment
 2200T    E    ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION                     invalid_xml_processing_instruction
 22030    E    ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE                        duplicate_json_object_key_value
+22031    E    ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION            invalid_argument_for_json_datetime_function
 22032    E    ERRCODE_INVALID_JSON_TEXT                                      invalid_json_text
 22033    E    ERRCODE_INVALID_JSON_SUBSCRIPT                                 invalid_json_subscript
 22034    E    ERRCODE_MORE_THAN_ONE_JSON_ITEM                                more_than_one_json_item
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 87335248a03..8e4b342c778 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9279,6 +9279,28 @@
   proname => 'jsonb_path_match', prorettype => 'bool',
   proargtypes => 'jsonb jsonpath jsonb bool', prosrc => 'jsonb_path_match' },
 
+{ oid => '6015', descr => 'jsonpath exists test with timezone',
+  proname => 'jsonb_path_exists_tz', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_exists_tz' },
+{ oid => '6016', descr => 'jsonpath query with timezone',
+  proname => 'jsonb_path_query_tz', provolatile => 's',
+  prorows => '1000', proretset => 't',
+  prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_query_tz' },
+{ oid => '6017', descr => 'jsonpath query wrapped into array with timezone',
+  proname => 'jsonb_path_query_array_tz', provolatile => 's',
+  prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_query_array_tz' },
+{ oid => '6018', descr => 'jsonpath query first item with timezone',
+  proname => 'jsonb_path_query_first_tz', provolatile => 's',
+  prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_query_first_tz' },
+{ oid => '6019', descr => 'jsonpath match with timezone',
+  proname => 'jsonb_path_match_tz', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_match_tz' },
+
 { oid => '4010', descr => 'implementation of @? operator',
   proname => 'jsonb_path_exists_opr', prorettype => 'bool',
   proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_path_exists_opr' },
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 40ad5fda928..f6b17c8aa2f 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -79,6 +79,7 @@ typedef enum JsonPathItemType
 	jpiFloor,					/* .floor() item method */
 	jpiCeiling,					/* .ceiling() item method */
 	jpiDouble,					/* .double() item method */
+	jpiDatetime,				/* .datetime() item method */
 	jpiKeyValue,				/* .keyvalue() item method */
 	jpiSubscript,				/* array subscript: 'expr' or 'expr TO expr' */
 	jpiLast,					/* LAST array subscript */
diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
index 31a871af028..750802989be 100644
--- a/src/test/regress/expected/jsonb_jsonpath.out
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -1658,6 +1658,532 @@ select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ lik
  "a\b"
 (1 row)
 
+select jsonb_path_query('null', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() is applied to not a string
+select jsonb_path_query('true', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() is applied to not a string
+select jsonb_path_query('1', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() is applied to not a string
+select jsonb_path_query('[]', '$.datetime()');
+ jsonb_path_query 
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'strict $.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() is applied to not a string
+select jsonb_path_query('{}', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() is applied to not a string
+select jsonb_path_query('""', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  unrecognized datetime format
+HINT:  use datetime template argument for explicit format specification
+select jsonb_path_query('"12:34"', '$.datetime("aaa")');
+ERROR:  trailing characters remain in input string after datetime format
+select jsonb_path_query('"aaaa"', '$.datetime("HH24")');
+ERROR:  invalid value "aa" for "HH24"
+DETAIL:  Value must be an integer.
+select jsonb '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")';
+ ?column? 
+----------
+ t
+(1 row)
+
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+ jsonb_path_query 
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+ jsonb_path_query 
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+ERROR:  trailing characters remain in input string after datetime format
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+ERROR:  trailing characters remain in input string after datetime format
+select jsonb_path_query('"10-03-2017 12:34"', '       $.datetime("dd-mm-yyyy HH24:MI").type()');
+       jsonb_path_query        
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+      jsonb_path_query      
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+     jsonb_path_query     
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+   jsonb_path_query    
+-----------------------
+ "time with time zone"
+(1 row)
+
+set time zone '+00';
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+ jsonb_path_query 
+------------------
+ "12:34:00"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone '+10';
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+ jsonb_path_query 
+------------------
+ "12:34:00"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone default;
+select jsonb_path_query('"2017-03-10"', '$.datetime().type()');
+ jsonb_path_query 
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"2017-03-10"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+       jsonb_path_query        
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:34:56"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()');
+      jsonb_path_query      
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:56+03:00"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()');
+      jsonb_path_query      
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:56+03:10"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime().type()');
+     jsonb_path_query     
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "12:34:56"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()');
+   jsonb_path_query    
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "12:34:56+03:00"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()');
+   jsonb_path_query    
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "12:34:56+03:10"
+(1 row)
+
+set time zone '+00';
+-- date comparison
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR:  can't convert value from date to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR:  can't convert value from date to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR:  can't convert value from date to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10"
+ "2017-03-10T00:00:00"
+ "2017-03-10T03:00:00+03:00"
+(3 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10"
+ "2017-03-11"
+ "2017-03-10T00:00:00"
+ "2017-03-10T12:34:56"
+ "2017-03-10T03:00:00+03:00"
+(5 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-09"
+ "2017-03-10T01:02:03+04:00"
+(2 rows)
+
+-- time comparison
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+ERROR:  can't convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+ERROR:  can't convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+ERROR:  can't convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00"
+ "12:35:00+00:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00"
+ "12:36:00"
+ "12:35:00+00:00"
+(3 rows)
+
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:34:00"
+ "12:35:00+01:00"
+ "13:35:00+01:00"
+(3 rows)
+
+-- timetz comparison
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR:  can't convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR:  can't convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR:  can't convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00+01:00"
+(1 row)
+
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00+01:00"
+ "12:36:00+01:00"
+ "12:35:00-02:00"
+ "11:35:00"
+ "12:35:00"
+(5 rows)
+
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:34:00+01:00"
+ "12:35:00+02:00"
+ "10:35:00"
+(3 rows)
+
+-- timestamp comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR:  can't convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR:  can't convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR:  can't convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T13:35:00+01:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T12:36:00"
+ "2017-03-10T13:35:00+01:00"
+ "2017-03-10T12:35:00-01:00"
+ "2017-03-11"
+(5 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:34:00"
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10"
+(3 rows)
+
+-- timestamptz comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR:  can't convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR:  can't convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR:  can't convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T11:35:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+set time zone default;
 -- jsonpath operators
 SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
  jsonb_path_query 
diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
index ecdd453942b..f5d5fa4fb8d 100644
--- a/src/test/regress/expected/jsonpath.out
+++ b/src/test/regress/expected/jsonpath.out
@@ -401,6 +401,18 @@ select '$.keyvalue().key'::jsonpath;
  $.keyvalue()."key"
 (1 row)
 
+select '$.datetime()'::jsonpath;
+   jsonpath   
+--------------
+ $.datetime()
+(1 row)
+
+select '$.datetime("datetime template")'::jsonpath;
+            jsonpath             
+---------------------------------
+ $.datetime("datetime template")
+(1 row)
+
 select '$ ? (@ starts with "abc")'::jsonpath;
         jsonpath         
 -------------------------
diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
index 733fbd4e0d0..2c16182e554 100644
--- a/src/test/regress/sql/jsonb_jsonpath.sql
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -346,6 +346,178 @@ select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ lik
 select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\B$" flag "iq")');
 select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\b$" flag "")');
 
+select jsonb_path_query('null', '$.datetime()');
+select jsonb_path_query('true', '$.datetime()');
+select jsonb_path_query('1', '$.datetime()');
+select jsonb_path_query('[]', '$.datetime()');
+select jsonb_path_query('[]', 'strict $.datetime()');
+select jsonb_path_query('{}', '$.datetime()');
+select jsonb_path_query('""', '$.datetime()');
+select jsonb_path_query('"12:34"', '$.datetime("aaa")');
+select jsonb_path_query('"aaaa"', '$.datetime("HH24")');
+
+select jsonb '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")';
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+
+select jsonb_path_query('"10-03-2017 12:34"', '       $.datetime("dd-mm-yyyy HH24:MI").type()');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+
+set time zone '+00';
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone '+10';
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone default;
+
+select jsonb_path_query('"2017-03-10"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()');
+select jsonb_path_query('"12:34:56"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56"', '$.datetime()');
+select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56 +3"', '$.datetime()');
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()');
+
+set time zone '+00';
+
+-- date comparison
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+
+-- time comparison
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+
+-- timetz comparison
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+
+-- timestamp comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+
+-- timestamptz comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+
+set time zone default;
+
 -- jsonpath operators
 
 SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
index 29ea77a4858..713d32400a7 100644
--- a/src/test/regress/sql/jsonpath.sql
+++ b/src/test/regress/sql/jsonpath.sql
@@ -72,6 +72,8 @@ select '"aaa".type()'::jsonpath;
 select 'true.type()'::jsonpath;
 select '$.double().floor().ceiling().abs()'::jsonpath;
 select '$.keyvalue().key'::jsonpath;
+select '$.datetime()'::jsonpath;
+select '$.datetime("datetime template")'::jsonpath;
 
 select '$ ? (@ starts with "abc")'::jsonpath;
 select '$ ? (@ starts with $var)'::jsonpath;
0003-error-suppression-for-datetime-2.patchapplication/octet-stream; name=0003-error-suppression-for-datetime-2.patchDownload
commit 53d051a3420f6250c86351a690109655187fb717
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Mon May 27 10:29:07 2019 +0300

    Error suppression support for upcoming jsonpath .datetime() method
    
    Add support of error suppression in some date and time manipulation functions
    as it's required for jsonpath .datetime() method support.  This commit doesn't
    use PG_TRY()/PG_CATCH() in order to implement that.  Instead, it provides
    internal versions of date and time functions used, which support error
    suppression.

diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 9e291b5c7bc..1456414e399 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -550,13 +550,15 @@ date_mii(PG_FUNCTION_ARGS)
 	PG_RETURN_DATEADT(result);
 }
 
+
 /*
- * Internal routines for promoting date to timestamp and timestamp with
- * time zone
+ * Promote date to timestamp.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero returned.
  */
-
-static Timestamp
-date2timestamp(DateADT dateVal)
+Timestamp
+date2timestamp_opt_error(DateADT dateVal, bool *have_error)
 {
 	Timestamp	result;
 
@@ -572,9 +574,19 @@ date2timestamp(DateADT dateVal)
 		 * boundary need be checked for overflow.
 		 */
 		if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("date out of range for timestamp")));
+		{
+			if (have_error)
+			{
+				*have_error = true;
+				return (Timestamp) 0;
+			}
+			else
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("date out of range for timestamp")));
+			}
+		}
 
 		/* date is days since 2000, timestamp is microseconds since same... */
 		result = dateVal * USECS_PER_DAY;
@@ -583,8 +595,23 @@ date2timestamp(DateADT dateVal)
 	return result;
 }
 
+/*
+ * Single-argument version of date2timestamp_opt_error().
+ */
 static TimestampTz
-date2timestamptz(DateADT dateVal)
+date2timestamp(DateADT dateVal)
+{
+	return date2timestamp_opt_error(dateVal, NULL);
+}
+
+/*
+ * Promote date to timestamp with time zone.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero returned.
+ */
+TimestampTz
+date2timestamptz_opt_error(DateADT dateVal, bool *have_error)
 {
 	TimestampTz result;
 	struct pg_tm tt,
@@ -603,9 +630,19 @@ date2timestamptz(DateADT dateVal)
 		 * boundary need be checked for overflow.
 		 */
 		if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("date out of range for timestamp")));
+		{
+			if (have_error)
+			{
+				*have_error = true;
+				return (TimestampTz) 0;
+			}
+			else
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("date out of range for timestamp")));
+			}
+		}
 
 		j2date(dateVal + POSTGRES_EPOCH_JDATE,
 			   &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday));
@@ -621,14 +658,33 @@ date2timestamptz(DateADT dateVal)
 		 * of time zone, check for allowed timestamp range after adding tz.
 		 */
 		if (!IS_VALID_TIMESTAMP(result))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("date out of range for timestamp")));
+		{
+			if (have_error)
+			{
+				*have_error = true;
+				return (TimestampTz) 0;
+			}
+			else
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("date out of range for timestamp")));
+			}
+		}
 	}
 
 	return result;
 }
 
+/*
+ * Single-argument version of date2timestamptz_opt_error().
+ */
+static TimestampTz
+date2timestamptz(DateADT dateVal)
+{
+	return date2timestamptz_opt_error(dateVal, NULL);
+}
+
 /*
  * date2timestamp_no_overflow
  *
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index fe6aa91b2e9..7e84ddab372 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -98,6 +98,43 @@
 #include "utils/numeric.h"
 #include "utils/pg_locale.h"
 
+/* ----------
+ * Convinience macros for error handling
+ * ----------
+ *
+ * Two macros below helps handling errors in functions, which takes
+ * 'bool *have_error' argument.  When this argument is not NULL, it's expected
+ * that function will suppress ereports when possible.  Instead it should
+ * return some default value and set *have_error flag.
+ *
+ * RETURN_ERROR() macro intended to wrap ereport() calls.  When have_error
+ * argument is provided, then instead of ereport'ing we set *have_error flag
+ * and go to on_error label.  It's supposed that jump resources will be freed
+ * and some 'default' value returned.
+ *
+ * CHECK_ERROR() jumps on_error label when *have_error flag is defined and set.
+ * It's supposed to be used for immediate exit from the function on error
+ * after call of another function with 'bool *have_error' argument.
+ */
+#define RETURN_ERROR(throw_error) \
+do { \
+	if (have_error) \
+	{ \
+		*have_error = true; \
+		goto on_error; \
+	} \
+	else \
+	{ \
+		throw_error; \
+	} \
+} while (0)
+
+#define CHECK_ERROR \
+do { \
+	if (have_error && *have_error) \
+		goto on_error; \
+} while (0)
+
 /* ----------
  * Routines type
  * ----------
@@ -1008,7 +1045,7 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 static void DCH_to_char(FormatNode *node, bool is_interval,
 						TmToChar *in, char *out, Oid collid);
 static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
-						  bool strict);
+						  bool strict, bool *have_error);
 
 #ifdef DEBUG_TO_FROM_CHAR
 static void dump_index(const KeyWord *k, const int *index);
@@ -1019,14 +1056,21 @@ static const char *get_th(char *num, int type);
 static char *str_numth(char *dest, char *num, int type);
 static int	adjust_partial_year_to_2020(int year);
 static int	strspace_len(char *str);
-static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode);
-static void from_char_set_int(int *dest, const int value, const FormatNode *node);
-static int	from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node);
-static int	from_char_parse_int(int *dest, char **src, FormatNode *node);
+static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode,
+							   bool *have_error);
+static void from_char_set_int(int *dest, const int value, const FormatNode *node,
+							  bool *have_error);
+static int	from_char_parse_int_len(int *dest, char **src, const int len,
+									FormatNode *node, bool *have_error);
+static int	from_char_parse_int(int *dest, char **src, FormatNode *node,
+								bool *have_error);
 static int	seq_search(char *name, const char *const *array, int type, int max, int *len);
-static int	from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
+static int	from_char_seq_search(int *dest, char **src,
+								 const char *const *array, int type, int max,
+								 FormatNode *node, bool *have_error);
 static void do_to_timestamp(text *date_txt, text *fmt, bool strict,
-							struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags);
+							struct pg_tm *tm, fsec_t *fsec, int *fprec,
+							int *flags, bool *have_error);
 static char *fill_str(char *str, int c, int max);
 static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
 static char *int_to_roman(int number);
@@ -2202,21 +2246,26 @@ strspace_len(char *str)
  *
  * Puke if the date mode has already been set, and the caller attempts to set
  * it to a conflicting mode.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
-from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode)
+from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode, bool *have_error)
 {
 	if (mode != FROM_CHAR_DATE_NONE)
 	{
 		if (tmfc->mode == FROM_CHAR_DATE_NONE)
 			tmfc->mode = mode;
 		else if (tmfc->mode != mode)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("invalid combination of date conventions"),
-					 errhint("Do not mix Gregorian and ISO week date "
-							 "conventions in a formatting template.")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("invalid combination of date conventions"),
+								  errhint("Do not mix Gregorian and ISO week date "
+										  "conventions in a formatting template."))));
 	}
+
+on_error:
+	return;
 }
 
 /*
@@ -2224,18 +2273,25 @@ from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode)
  *
  * Puke if the destination integer has previously been set to some other
  * non-zero value.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
-from_char_set_int(int *dest, const int value, const FormatNode *node)
+from_char_set_int(int *dest, const int value, const FormatNode *node,
+				  bool *have_error)
 {
 	if (*dest != 0 && *dest != value)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("conflicting values for \"%s\" field in formatting string",
-						node->key->name),
-				 errdetail("This value contradicts a previous setting for "
-						   "the same field type.")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("conflicting values for \"%s\" field in "
+									 "formatting string",
+									 node->key->name),
+							  errdetail("This value contradicts a previous setting "
+										"for the same field type."))));
 	*dest = value;
+
+on_error:
+	return;
 }
 
 /*
@@ -2257,9 +2313,13 @@ from_char_set_int(int *dest, const int value, const FormatNode *node)
  * Note that from_char_parse_int() provides a more convenient wrapper where
  * the length of the field is the same as the length of the format keyword (as
  * with DD and MI).
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and -1 is returned.
  */
 static int
-from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
+from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node,
+						bool *have_error)
 {
 	long		result;
 	char		copy[DCH_MAX_ITEM_SIZ + 1];
@@ -2292,51 +2352,60 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
 		char	   *last;
 
 		if (used < len)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("source string too short for \"%s\" formatting field",
-							node->key->name),
-					 errdetail("Field requires %d characters, but only %d "
-							   "remain.",
-							   len, used),
-					 errhint("If your source string is not fixed-width, try "
-							 "using the \"FM\" modifier.")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("source string too short for \"%s\" "
+										 "formatting field",
+										 node->key->name),
+								  errdetail("Field requires %d characters, "
+											"but only %d remain.",
+											len, used),
+								  errhint("If your source string is not fixed-width, "
+										  "try using the \"FM\" modifier."))));
 
 		errno = 0;
 		result = strtol(copy, &last, 10);
 		used = last - copy;
 
 		if (used > 0 && used < len)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("invalid value \"%s\" for \"%s\"",
-							copy, node->key->name),
-					 errdetail("Field requires %d characters, but only %d "
-							   "could be parsed.", len, used),
-					 errhint("If your source string is not fixed-width, try "
-							 "using the \"FM\" modifier.")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("invalid value \"%s\" for \"%s\"",
+										 copy, node->key->name),
+								  errdetail("Field requires %d characters, "
+											"but only %d could be parsed.",
+											len, used),
+								  errhint("If your source string is not fixed-width, "
+										  "try using the \"FM\" modifier."))));
 
 		*src += used;
 	}
 
 	if (*src == init)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("invalid value \"%s\" for \"%s\"",
-						copy, node->key->name),
-				 errdetail("Value must be an integer.")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("invalid value \"%s\" for \"%s\"",
+									 copy, node->key->name),
+							  errdetail("Value must be an integer."))));
 
 	if (errno == ERANGE || result < INT_MIN || result > INT_MAX)
-		ereport(ERROR,
-				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-				 errmsg("value for \"%s\" in source string is out of range",
-						node->key->name),
-				 errdetail("Value must be in the range %d to %d.",
-						   INT_MIN, INT_MAX)));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							  errmsg("value for \"%s\" in source string is out of range",
+									 node->key->name),
+							  errdetail("Value must be in the range %d to %d.",
+										INT_MIN, INT_MAX))));
 
 	if (dest != NULL)
-		from_char_set_int(dest, (int) result, node);
+	{
+		from_char_set_int(dest, (int) result, node, have_error);
+		CHECK_ERROR;
+	}
+
 	return *src - init;
+
+on_error:
+	return -1;
 }
 
 /*
@@ -2349,9 +2418,9 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
  * required length explicitly.
  */
 static int
-from_char_parse_int(int *dest, char **src, FormatNode *node)
+from_char_parse_int(int *dest, char **src, FormatNode *node, bool *have_error)
 {
-	return from_char_parse_int_len(dest, src, node->key->len, node);
+	return from_char_parse_int_len(dest, src, node->key->len, node, have_error);
 }
 
 /* ----------
@@ -2434,11 +2503,12 @@ seq_search(char *name, const char *const *array, int type, int max, int *len)
  * pointed to by 'dest', advance 'src' to the end of the part of the string
  * which matched, and return the number of characters consumed.
  *
- * If the string doesn't match, throw an error.
+ * If the string doesn't match, throw an error if 'have_error' is NULL,
+ * otherwise set '*have_error' and return -1.
  */
 static int
-from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max,
-					 FormatNode *node)
+from_char_seq_search(int *dest, char **src, const char *const *array, int type,
+					 int max, FormatNode *node, bool *have_error)
 {
 	int			len;
 
@@ -2450,15 +2520,18 @@ from_char_seq_search(int *dest, char **src, const char *const *array, int type,
 		Assert(max <= DCH_MAX_ITEM_SIZ);
 		strlcpy(copy, *src, max + 1);
 
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("invalid value \"%s\" for \"%s\"",
-						copy, node->key->name),
-				 errdetail("The given value did not match any of the allowed "
-						   "values for this field.")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("invalid value \"%s\" for \"%s\"",
+									 copy, node->key->name),
+							  errdetail("The given value did not match any of "
+										"the allowed values for this field."))));
 	}
 	*src += len;
 	return len;
+
+on_error:
+	return -1;
 }
 
 /* ----------
@@ -3060,10 +3133,13 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
  *
  * Note: we currently don't have any to_interval() function, so there
  * is no need here for INVALID_FOR_INTERVAL checks.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  * ----------
  */
 static void
-DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
+DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict,
+			  bool *have_error)
 {
 	FormatNode *n;
 	char	   *s;
@@ -3146,7 +3222,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 			continue;
 		}
 
-		from_char_set_mode(out, n->key->date_mode);
+		from_char_set_mode(out, n->key->date_mode, have_error);
+		CHECK_ERROR;
 
 		switch (n->key->id)
 		{
@@ -3158,8 +3235,10 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 			case DCH_a_m:
 			case DCH_p_m:
 				from_char_seq_search(&value, &s, ampm_strings_long,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->pm, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->pm, value % 2, n, have_error);
+				CHECK_ERROR;
 				out->clock = CLOCK_12_HOUR;
 				break;
 			case DCH_AM:
@@ -3167,30 +3246,37 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 			case DCH_am:
 			case DCH_pm:
 				from_char_seq_search(&value, &s, ampm_strings,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->pm, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->pm, value % 2, n, have_error);
+				CHECK_ERROR;
 				out->clock = CLOCK_12_HOUR;
 				break;
 			case DCH_HH:
 			case DCH_HH12:
-				from_char_parse_int_len(&out->hh, &s, 2, n);
+				from_char_parse_int_len(&out->hh, &s, 2, n, have_error);
+				CHECK_ERROR;
 				out->clock = CLOCK_12_HOUR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_HH24:
-				from_char_parse_int_len(&out->hh, &s, 2, n);
+				from_char_parse_int_len(&out->hh, &s, 2, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_MI:
-				from_char_parse_int(&out->mi, &s, n);
+				from_char_parse_int(&out->mi, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_SS:
-				from_char_parse_int(&out->ss, &s, n);
+				from_char_parse_int(&out->ss, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_MS:		/* millisecond */
-				len = from_char_parse_int_len(&out->ms, &s, 3, n);
+				len = from_char_parse_int_len(&out->ms, &s, 3, n, have_error);
+				CHECK_ERROR;
 
 				/*
 				 * 25 is 0.25 and 250 is 0.25 too; 025 is 0.025 and not 0.25
@@ -3211,7 +3297,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 			case DCH_US:		/* microsecond */
 				len = from_char_parse_int_len(&out->us, &s,
 											  n->key->id == DCH_US ? 6 :
-											  out->ff, n);
+											  out->ff, n, have_error);
+				CHECK_ERROR;
 
 				out->us *= len == 1 ? 100000 :
 					len == 2 ? 10000 :
@@ -3222,16 +3309,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_SSSS:
-				from_char_parse_int(&out->ssss, &s, n);
+				from_char_parse_int(&out->ssss, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_tz:
 			case DCH_TZ:
 			case DCH_OF:
-				ereport(ERROR,
-						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						 errmsg("formatting field \"%s\" is only supported in to_char",
-								n->key->name)));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									  errmsg("formatting field \"%s\" is only supported in to_char",
+											 n->key->name))));
+				CHECK_ERROR;
 				break;
 			case DCH_TZH:
 
@@ -3255,82 +3344,102 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 						out->tzsign = +1;
 				}
 
-				from_char_parse_int_len(&out->tzh, &s, 2, n);
+				from_char_parse_int_len(&out->tzh, &s, 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_TZM:
 				/* assign positive timezone sign if TZH was not seen before */
 				if (!out->tzsign)
 					out->tzsign = +1;
-				from_char_parse_int_len(&out->tzm, &s, 2, n);
+				from_char_parse_int_len(&out->tzm, &s, 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_A_D:
 			case DCH_B_C:
 			case DCH_a_d:
 			case DCH_b_c:
 				from_char_seq_search(&value, &s, adbc_strings_long,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->bc, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->bc, value % 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_AD:
 			case DCH_BC:
 			case DCH_ad:
 			case DCH_bc:
 				from_char_seq_search(&value, &s, adbc_strings,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->bc, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->bc, value % 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_MONTH:
 			case DCH_Month:
 			case DCH_month:
 				from_char_seq_search(&value, &s, months_full, ONE_UPPER,
-									 MAX_MONTH_LEN, n);
-				from_char_set_int(&out->mm, value + 1, n);
+									 MAX_MONTH_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, value + 1, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_MON:
 			case DCH_Mon:
 			case DCH_mon:
 				from_char_seq_search(&value, &s, months, ONE_UPPER,
-									 MAX_MON_LEN, n);
-				from_char_set_int(&out->mm, value + 1, n);
+									 MAX_MON_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, value + 1, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_MM:
-				from_char_parse_int(&out->mm, &s, n);
+				from_char_parse_int(&out->mm, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_DAY:
 			case DCH_Day:
 			case DCH_day:
 				from_char_seq_search(&value, &s, days, ONE_UPPER,
-									 MAX_DAY_LEN, n);
-				from_char_set_int(&out->d, value, n);
+									 MAX_DAY_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->d, value, n, have_error);
+				CHECK_ERROR;
 				out->d++;
 				break;
 			case DCH_DY:
 			case DCH_Dy:
 			case DCH_dy:
 				from_char_seq_search(&value, &s, days, ONE_UPPER,
-									 MAX_DY_LEN, n);
-				from_char_set_int(&out->d, value, n);
+									 MAX_DY_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->d, value, n, have_error);
+				CHECK_ERROR;
 				out->d++;
 				break;
 			case DCH_DDD:
-				from_char_parse_int(&out->ddd, &s, n);
+				from_char_parse_int(&out->ddd, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_IDDD:
-				from_char_parse_int_len(&out->ddd, &s, 3, n);
+				from_char_parse_int_len(&out->ddd, &s, 3, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_DD:
-				from_char_parse_int(&out->dd, &s, n);
+				from_char_parse_int(&out->dd, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_D:
-				from_char_parse_int(&out->d, &s, n);
+				from_char_parse_int(&out->d, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_ID:
-				from_char_parse_int_len(&out->d, &s, 1, n);
+				from_char_parse_int_len(&out->d, &s, 1, n, have_error);
+				CHECK_ERROR;
 				/* Shift numbering to match Gregorian where Sunday = 1 */
 				if (++out->d > 7)
 					out->d = 1;
@@ -3338,7 +3447,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 				break;
 			case DCH_WW:
 			case DCH_IW:
-				from_char_parse_int(&out->ww, &s, n);
+				from_char_parse_int(&out->ww, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Q:
@@ -3353,11 +3463,13 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 				 * We still parse the source string for an integer, but it
 				 * isn't stored anywhere in 'out'.
 				 */
-				from_char_parse_int((int *) NULL, &s, n);
+				from_char_parse_int((int *) NULL, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_CC:
-				from_char_parse_int(&out->cc, &s, n);
+				from_char_parse_int(&out->cc, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Y_YYY:
@@ -3369,11 +3481,12 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 
 					matched = sscanf(s, "%d,%03d%n", &millennia, &years, &nch);
 					if (matched < 2)
-						ereport(ERROR,
-								(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-								 errmsg("invalid input string for \"Y,YYY\"")));
+						RETURN_ERROR(ereport(ERROR,
+											 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+											  errmsg("invalid input string for \"Y,YYY\""))));
 					years += (millennia * 1000);
-					from_char_set_int(&out->year, years, n);
+					from_char_set_int(&out->year, years, n, have_error);
+					CHECK_ERROR;
 					out->yysz = 4;
 					s += nch;
 					SKIP_THth(s, n->suffix);
@@ -3381,47 +3494,62 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 				break;
 			case DCH_YYYY:
 			case DCH_IYYY:
-				from_char_parse_int(&out->year, &s, n);
+				from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
 				out->yysz = 4;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_YYY:
 			case DCH_IYY:
-				if (from_char_parse_int(&out->year, &s, n) < 4)
+				len = from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
+				if (len < 4)
 					out->year = adjust_partial_year_to_2020(out->year);
 				out->yysz = 3;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_YY:
 			case DCH_IY:
-				if (from_char_parse_int(&out->year, &s, n) < 4)
+				len = from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
+				if (len < 4)
 					out->year = adjust_partial_year_to_2020(out->year);
 				out->yysz = 2;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Y:
 			case DCH_I:
-				if (from_char_parse_int(&out->year, &s, n) < 4)
+				len = from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
+				if (len < 4)
 					out->year = adjust_partial_year_to_2020(out->year);
 				out->yysz = 1;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_RM:
 				from_char_seq_search(&value, &s, rm_months_upper,
-									 ALL_UPPER, MAX_RM_LEN, n);
-				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n);
+									 ALL_UPPER, MAX_RM_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value,
+								  n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_rm:
 				from_char_seq_search(&value, &s, rm_months_lower,
-									 ALL_LOWER, MAX_RM_LEN, n);
-				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n);
+									 ALL_LOWER, MAX_RM_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value,
+								  n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_W:
-				from_char_parse_int(&out->w, &s, n);
+				from_char_parse_int(&out->w, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_J:
-				from_char_parse_int(&out->j, &s, n);
+				from_char_parse_int(&out->j, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 		}
@@ -3441,19 +3569,22 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 	if (strict)
 	{
 		if (n->type != NODE_TYPE_END)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("input string is too short for datetime format")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("input string is too short for datetime format"))));
 
 		while (*s != '\0' && isspace((unsigned char) *s))
 			s++;
 
 		if (*s != '\0')
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("trailing characters remain in input string after "
-							"datetime format")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("trailing characters remain in input string "
+										 "after datetime format"))));
 	}
+
+on_error:
+	return;
 }
 
 /*
@@ -3474,9 +3605,13 @@ DCH_prevent_counter_overflow(void)
 	}
 }
 
-/* Get mask of date/time/zone components present in format nodes. */
+/*
+ * Get mask of date/time/zone components present in format nodes.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
+ */
 static int
-DCH_datetime_type(FormatNode *node)
+DCH_datetime_type(FormatNode *node, bool *have_error)
 {
 	FormatNode *n;
 	int			flags = 0;
@@ -3517,10 +3652,10 @@ DCH_datetime_type(FormatNode *node)
 			case DCH_tz:
 			case DCH_TZ:
 			case DCH_OF:
-				ereport(ERROR,
-						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						 errmsg("formatting field \"%s\" is only supported in to_char",
-								n->key->name)));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									  errmsg("formatting field \"%s\" is only supported in to_char",
+											 n->key->name))));
 				flags |= DCH_ZONED;
 				break;
 			case DCH_TZH:
@@ -3574,6 +3709,7 @@ DCH_datetime_type(FormatNode *node)
 		}
 	}
 
+on_error:
 	return flags;
 }
 
@@ -3867,7 +4003,7 @@ to_timestamp(PG_FUNCTION_ARGS)
 	fsec_t		fsec;
 	int			fprec;
 
-	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL, NULL);
 
 	/* Use the specified time zone, if any. */
 	if (tm.tm_zone)
@@ -3906,7 +4042,7 @@ to_date(PG_FUNCTION_ARGS)
 	struct pg_tm tm;
 	fsec_t		fsec;
 
-	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL, NULL);
 
 	/* Prevent overflow in Julian-day routines */
 	if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3935,17 +4071,21 @@ to_date(PG_FUNCTION_ARGS)
  * Default time-zone for tz types is specified with 'tzname'.  If 'tzname' is
  * NULL and the input string does not contain zone components then "missing tz"
  * error is thrown.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero value is returned.
  */
 Datum
 parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
-			   int32 *typmod, int *tz)
+			   int32 *typmod, int *tz, bool *have_error)
 {
 	struct pg_tm tm;
 	fsec_t		fsec;
 	int			fprec = 0;
 	int			flags;
 
-	do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags);
+	do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags, have_error);
+	CHECK_ERROR;
 
 	*typmod = fprec ? fprec : -1;	/* fractional part precision */
 
@@ -3966,16 +4106,22 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 				}
 				else
 				{
-					if (*tz == PG_INT32_MIN)
-						ereport(ERROR,
-								(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-								 errmsg("missing time-zone in input string for type timestamptz")));
+					/*
+					 * Time zone is present in format string, but not in input
+					 * string.  Assuming do_to_timestamp() triggers no error
+					 * this should be possible only in non-strict case.
+					 */
+					Assert(!strict);
+
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+										  errmsg("missing time-zone in input string for type timestamptz"))));
 				}
 
 				if (tm2timestamp(&tm, fsec, tz, &result) != 0)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("timestamptz out of range")));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("timestamptz out of range"))));
 
 				AdjustTimestampForTypmod(&result, *typmod);
 
@@ -3987,9 +4133,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 				Timestamp	result;
 
 				if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("timestamp out of range")));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("timestamp out of range"))));
 
 				AdjustTimestampForTypmod(&result, *typmod);
 
@@ -4001,9 +4147,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 		{
 			if (flags & DCH_ZONED)
 			{
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-						 errmsg("datetime format is zoned but not timed")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+									  errmsg("datetime format is zoned but not timed"))));
 			}
 			else
 			{
@@ -4011,20 +4157,20 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 
 				/* Prevent overflow in Julian-day routines */
 				if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("date out of range: \"%s\"",
-									text_to_cstring(date_txt))));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("date out of range: \"%s\"",
+												 text_to_cstring(date_txt)))));
 
 				result = 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(result))
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("date out of range: \"%s\"",
-									text_to_cstring(date_txt))));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("date out of range: \"%s\"",
+												 text_to_cstring(date_txt)))));
 
 				*typid = DATEOID;
 				return DateADTGetDatum(result);
@@ -4042,20 +4188,26 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 				int			dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
 
 				if (dterr)
-					DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz");
+					RETURN_ERROR(DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz"));
 			}
 			else
 			{
-				if (*tz == PG_INT32_MIN)
-					ereport(ERROR,
-							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-							 errmsg("missing time-zone in input string for type timetz")));
+				/*
+				 * Time zone is present in format string, but not in input
+				 * string.  Assuming do_to_timestamp() triggers no error this
+				 * should be possible only in non-strict case.
+				 */
+				Assert(!strict);
+
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+									  errmsg("missing time-zone in input string for type timetz"))));
 			}
 
 			if (tm2timetz(&tm, fsec, *tz, result) != 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-						 errmsg("timetz out of range")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+									  errmsg("timetz out of range"))));
 
 			AdjustTimeForTypmod(&result->time, *typmod);
 
@@ -4067,9 +4219,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 			TimeADT		result;
 
 			if (tm2time(&tm, fsec, &result) != 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-						 errmsg("time out of range")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+									  errmsg("time out of range"))));
 
 			AdjustTimeForTypmod(&result, *typmod);
 
@@ -4079,11 +4231,12 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 	}
 	else
 	{
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("datetime format is not dated and not timed")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("datetime format is not dated and not timed"))));
 	}
 
+on_error:
 	return (Datum) 0;
 }
 
@@ -4104,16 +4257,19 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
  *
  * 'strict' enables error reporting on unmatched trailing characters in input or
  * format strings patterns.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
-do_to_timestamp(text *date_txt, text *fmt, bool strict,
-				struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags)
+do_to_timestamp(text *date_txt, text *fmt, bool strict, struct pg_tm *tm,
+				fsec_t *fsec, int *fprec, int *flags, bool *have_error)
 {
-	FormatNode *format;
+	FormatNode *format = NULL;
 	TmFromChar	tmfc;
 	int			fmt_len;
 	char	   *date_str;
 	int			fmask;
+	bool		incache;
 
 	date_str = text_to_cstring(date_txt);
 
@@ -4127,7 +4283,6 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 	if (fmt_len)
 	{
 		char	   *fmt_str;
-		bool		incache;
 
 		fmt_str = text_to_cstring(fmt);
 
@@ -4160,15 +4315,21 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 		/* dump_index(DCH_keywords, DCH_index); */
 #endif
 
-		DCH_from_char(format, date_str, &tmfc, strict);
+		DCH_from_char(format, date_str, &tmfc, strict, have_error);
+		CHECK_ERROR;
 
 		pfree(fmt_str);
 
 		if (flags)
-			*flags = DCH_datetime_type(format);
+			*flags = DCH_datetime_type(format, have_error);
 
 		if (!incache)
+		{
 			pfree(format);
+			format = NULL;
+		}
+
+		CHECK_ERROR;
 	}
 
 	DEBUG_TMFC(&tmfc);
@@ -4197,11 +4358,13 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 	if (tmfc.clock == CLOCK_12_HOUR)
 	{
 		if (tm->tm_hour < 1 || tm->tm_hour > HOURS_PER_DAY / 2)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("hour \"%d\" is invalid for the 12-hour clock",
-							tm->tm_hour),
-					 errhint("Use the 24-hour clock, or give an hour between 1 and 12.")));
+		{
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("hour \"%d\" is invalid for the 12-hour clock",
+										 tm->tm_hour),
+								  errhint("Use the 24-hour clock, or give an hour between 1 and 12."))));
+		}
 
 		if (tmfc.pm && tm->tm_hour < HOURS_PER_DAY / 2)
 			tm->tm_hour += HOURS_PER_DAY / 2;
@@ -4305,9 +4468,11 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 		 */
 
 		if (!tm->tm_year && !tmfc.bc)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("cannot calculate day of year without year information")));
+		{
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("cannot calculate day of year without year information"))));
+		}
 
 		if (tmfc.mode == FROM_CHAR_DATE_ISOWEEK)
 		{
@@ -4364,7 +4529,7 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 			 * said DTERR_MD_FIELD_OVERFLOW, because we don't want to print an
 			 * irrelevant hint about datestyle.
 			 */
-			DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp");
+			RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp"));
 		}
 	}
 
@@ -4373,7 +4538,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 		tm->tm_min < 0 || tm->tm_min >= MINS_PER_HOUR ||
 		tm->tm_sec < 0 || tm->tm_sec >= SECS_PER_MINUTE ||
 		*fsec < INT64CONST(0) || *fsec >= USECS_PER_SEC)
-		DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp");
+	{
+		RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp"));
+	}
 
 	/* Save parsed time-zone into tm->tm_zone if it was specified */
 	if (tmfc.tzsign)
@@ -4382,7 +4549,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 
 		if (tmfc.tzh < 0 || tmfc.tzh > MAX_TZDISP_HOUR ||
 			tmfc.tzm < 0 || tmfc.tzm >= MINS_PER_HOUR)
-			DateTimeParseError(DTERR_TZDISP_OVERFLOW, date_str, "timestamp");
+		{
+			RETURN_ERROR(DateTimeParseError(DTERR_TZDISP_OVERFLOW, date_str, "timestamp"));
+		}
 
 		tz = psprintf("%c%02d:%02d",
 					  tmfc.tzsign > 0 ? '+' : '-', tmfc.tzh, tmfc.tzm);
@@ -4392,6 +4561,11 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 
 	DEBUG_TM(tm);
 
+on_error:
+
+	if (format && !incache)
+		pfree(format);
+
 	pfree(date_str);
 }
 
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 2931bd58c0d..80b8d656f2f 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -329,11 +329,11 @@ timestamp_scale(PG_FUNCTION_ARGS)
 }
 
 /*
- * AdjustTimestampForTypmod --- round off a timestamp to suit given typmod
+ * AdjustTimestampForTypmodError --- round off a timestamp to suit given typmod
  * Works for either timestamp or timestamptz.
  */
-void
-AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
+bool
+AdjustTimestampForTypmodError(Timestamp *time, int32 typmod, bool *error)
 {
 	static const int64 TimestampScales[MAX_TIMESTAMP_PRECISION + 1] = {
 		INT64CONST(1000000),
@@ -359,10 +359,18 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
 		&& (typmod != -1) && (typmod != MAX_TIMESTAMP_PRECISION))
 	{
 		if (typmod < 0 || typmod > MAX_TIMESTAMP_PRECISION)
+		{
+			if (error)
+			{
+				*error = true;
+				return false;
+			}
+
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("timestamp(%d) precision must be between %d and %d",
 							typmod, 0, MAX_TIMESTAMP_PRECISION)));
+		}
 
 		if (*time >= INT64CONST(0))
 		{
@@ -375,8 +383,15 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
 					  * TimestampScales[typmod]);
 		}
 	}
+
+	return true;
 }
 
+void
+AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
+{
+	(void) AdjustTimestampForTypmodError(time, typmod, NULL);
+}
 
 /* timestamptz_in()
  * Convert a string to internal form.
@@ -5172,8 +5187,15 @@ timestamp_timestamptz(PG_FUNCTION_ARGS)
 	PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
 }
 
-static TimestampTz
-timestamp2timestamptz(Timestamp timestamp)
+/*
+ * Convert timestamp to timestamp with time zone.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero returned.
+ */
+
+TimestampTz
+timestamp2timestamptz_opt_error(Timestamp timestamp, bool *have_error)
 {
 	TimestampTz result;
 	struct pg_tm tt,
@@ -5182,23 +5204,33 @@ timestamp2timestamptz(Timestamp timestamp)
 	int			tz;
 
 	if (TIMESTAMP_NOT_FINITE(timestamp))
-		result = timestamp;
-	else
-	{
-		if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("timestamp out of range")));
+		return timestamp;
 
+	if (!timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL))
+	{
 		tz = DetermineTimeZoneOffset(tm, session_timezone);
 
-		if (tm2timestamp(tm, fsec, &tz, &result) != 0)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("timestamp out of range")));
+		if (!tm2timestamp(tm, fsec, &tz, &result))
+			return result;
 	}
 
-	return result;
+	if (have_error)
+		*have_error = true;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+				 errmsg("timestamp out of range")));
+
+	return 0;
+}
+
+/*
+ * Single-argument version of timestamp2timestamptz_opt_error().
+ */
+static TimestampTz
+timestamp2timestamptz(Timestamp timestamp)
+{
+	return timestamp2timestamptz_opt_error(timestamp, NULL);
 }
 
 /* timestamptz_timestamp()
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index bd15bfa5bb0..c29f13aaf04 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -70,6 +70,8 @@ typedef struct
 /* date.c */
 extern int32 anytime_typmod_check(bool istz, int32 typmod);
 extern double date2timestamp_no_overflow(DateADT dateVal);
+extern Timestamp date2timestamp_opt_error(DateADT dateVal, bool *have_error);
+extern TimestampTz date2timestamptz_opt_error(DateADT dateVal, bool *have_error);
 extern void EncodeSpecialDate(DateADT dt, char *str);
 extern DateADT GetSQLCurrentDate(void);
 extern TimeTzADT *GetSQLCurrentTime(int32 typmod);
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index 0cafdd26538..5ebf336071b 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -337,5 +337,7 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
 extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
 
 extern void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
+extern bool AdjustTimestampForTypmodError(Timestamp *time, int32 typmod,
+										  bool *error);
 
 #endif							/* DATETIME_H */
diff --git a/src/include/utils/formatting.h b/src/include/utils/formatting.h
index a390302e89c..1a2e2a9caa8 100644
--- a/src/include/utils/formatting.h
+++ b/src/include/utils/formatting.h
@@ -29,6 +29,7 @@ extern char *asc_toupper(const char *buff, size_t nbytes);
 extern char *asc_initcap(const char *buff, size_t nbytes);
 
 extern Datum parse_datetime(text *date_txt, text *fmt, bool strict,
-							Oid *typid, int32 *typmod, int *tz);
+							Oid *typid, int32 *typmod, int *tz,
+							bool *have_error);
 
 #endif
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index ea16190ec3d..e884d4405f5 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -97,6 +97,9 @@ extern int	timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
 /* timestamp comparison works for timestamptz also */
 #define timestamptz_cmp_internal(dt1,dt2)	timestamp_cmp_internal(dt1, dt2)
 
+extern TimestampTz timestamp2timestamptz_opt_error(Timestamp timestamp,
+												   bool *have_error);
+
 extern int	isoweek2j(int year, int week);
 extern void isoweek2date(int woy, int *year, int *mon, int *mday);
 extern void isoweekdate2date(int isoweek, int wday, int *year, int *mon, int *mday);
#3Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Alexander Korotkov (#2)
4 attachment(s)
Re: Support for jsonpath .datetime() method

On Mon, Jul 1, 2019 at 7:28 PM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

On Tue, May 28, 2019 at 8:55 AM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

Attached patchset implements jsonpath .datetime() method.

Revised patchset is attached. Some inconsistencies around
parse_datetime() function are fixed. Rebased to current master as
well.

I found commitfest.cputube.org is unhappy with this patchset because
of gcc warning. Fixed in attached patchset.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0002-datetime-conversion-for-jsonpath-3.patchapplication/octet-stream; name=0002-datetime-conversion-for-jsonpath-3.patchDownload
commit cd3f24e54f19bf4e78f10b3c04addad593bd7a65
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Wed Jan 23 05:55:49 2019 +0300

    Improve datetime conversion for upcoming jsonpath .datetime() method
    
    Jsonpath language (part of SQL/JSON standard) includes functions for datetime
    conversion.  In order to support that, we have to extend our infrastructure
    in following ways.
    
      1. FF1-FF6 format patterns implementing different fractions of second.  FF3
         and FF6 are effectively just synonyms for MS and US.  But other fractions
         were not implemented yet.
      2. to_datetime() internal function, which dynamically determines result
         datatype depending on format string.
      3. Strict parsing mode, which doesn't allow trailing spaces and unmatched
         format patterns.
    
    The first improvement is already user-visible and can use used in datetime
    parsing/printing functions.  Other improvements are internal, they will be
    user-visible together with jsonpath.
    
    Code was written by Nikita Glukhov and Teodor Sigaev, revised by me.
    Documentation was written by Oleg Bartunov and Liudmila Mantrova.  The work
    was inspired by Oleg Bartunov.
    
    Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
    Author: Nikita Glukhov, Teodor Sigaev, Alexander Korotkov, Oleg Bartunov, Liudmila Mantrova
    Reviewed-by: Andrew Dunstan, Pavel Stehule, Tomas Vondra, Alexander Korotkov

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 0be8b8b95d2..b27edc804bd 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -6146,6 +6146,30 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
         <entry><literal>US</literal></entry>
         <entry>microsecond (000000-999999)</entry>
        </row>
+       <row>
+        <entry><literal>FF1</literal></entry>
+        <entry>decisecond (0-9)</entry>
+       </row>
+       <row>
+        <entry><literal>FF2</literal></entry>
+        <entry>centisecond (00-99)</entry>
+       </row>
+       <row>
+        <entry><literal>FF3</literal></entry>
+        <entry>millisecond (000-999)</entry>
+       </row>
+       <row>
+        <entry><literal>FF4</literal></entry>
+        <entry>tenth of a millisecond (0000-9999)</entry>
+       </row>
+       <row>
+        <entry><literal>FF5</literal></entry>
+        <entry>hundredth of a millisecond (00000-99999)</entry>
+       </row>
+       <row>
+        <entry><literal>FF6</literal></entry>
+        <entry>microsecond (000000-999999)</entry>
+       </row>
        <row>
         <entry><literal>SSSS</literal></entry>
         <entry>seconds past midnight (0-86399)</entry>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 4b1afb10f92..9e291b5c7bc 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -41,11 +41,6 @@
 #endif
 
 
-static int	tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
-static int	tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
-static void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
-
-
 /* common code for timetypmodin and timetztypmodin */
 static int32
 anytime_typmodin(bool istz, ArrayType *ta)
@@ -1203,7 +1198,7 @@ time_in(PG_FUNCTION_ARGS)
 /* tm2time()
  * Convert a tm structure to a time data type.
  */
-static int
+int
 tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result)
 {
 	*result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec)
@@ -1379,7 +1374,7 @@ time_scale(PG_FUNCTION_ARGS)
  * have a fundamental tie together but rather a coincidence of
  * implementation. - thomas
  */
-static void
+void
 AdjustTimeForTypmod(TimeADT *time, int32 typmod)
 {
 	static const int64 TimeScales[MAX_TIME_PRECISION + 1] = {
@@ -1957,7 +1952,7 @@ time_part(PG_FUNCTION_ARGS)
 /* tm2timetz()
  * Convert a tm structure to a time data type.
  */
-static int
+int
 tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result)
 {
 	result->time = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) *
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 206576d4bd3..fe6aa91b2e9 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -86,6 +86,7 @@
 #endif
 
 #include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
 #include "mb/pg_wchar.h"
 #include "utils/builtins.h"
 #include "utils/date.h"
@@ -436,7 +437,8 @@ typedef struct
 				clock,			/* 12 or 24 hour clock? */
 				tzsign,			/* +1, -1 or 0 if timezone info is absent */
 				tzh,
-				tzm;
+				tzm,
+				ff;				/* fractional precision */
 } TmFromChar;
 
 #define ZERO_tmfc(_X) memset(_X, 0, sizeof(TmFromChar))
@@ -596,6 +598,12 @@ typedef enum
 	DCH_Day,
 	DCH_Dy,
 	DCH_D,
+	DCH_FF1,
+	DCH_FF2,
+	DCH_FF3,
+	DCH_FF4,
+	DCH_FF5,
+	DCH_FF6,
 	DCH_FX,						/* global suffix */
 	DCH_HH24,
 	DCH_HH12,
@@ -645,6 +653,12 @@ typedef enum
 	DCH_dd,
 	DCH_dy,
 	DCH_d,
+	DCH_ff1,
+	DCH_ff2,
+	DCH_ff3,
+	DCH_ff4,
+	DCH_ff5,
+	DCH_ff6,
 	DCH_fx,
 	DCH_hh24,
 	DCH_hh12,
@@ -745,7 +759,13 @@ static const KeyWord DCH_keywords[] = {
 	{"Day", 3, DCH_Day, false, FROM_CHAR_DATE_NONE},
 	{"Dy", 2, DCH_Dy, false, FROM_CHAR_DATE_NONE},
 	{"D", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
-	{"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},	/* F */
+	{"FF1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE},	/* F */
+	{"FF2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE},
+	{"FF3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE},
+	{"FF4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE},
+	{"FF5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE},
+	{"FF6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE},
+	{"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
 	{"HH24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE},	/* H */
 	{"HH12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
 	{"HH", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
@@ -794,7 +814,13 @@ static const KeyWord DCH_keywords[] = {
 	{"dd", 2, DCH_DD, true, FROM_CHAR_DATE_GREGORIAN},
 	{"dy", 2, DCH_dy, false, FROM_CHAR_DATE_NONE},
 	{"d", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
-	{"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},	/* f */
+	{"ff1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE},	/* f */
+	{"ff2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE},
+	{"ff3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE},
+	{"ff4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE},
+	{"ff5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE},
+	{"ff6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE},
+	{"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
 	{"hh24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE},	/* h */
 	{"hh12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
 	{"hh", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
@@ -895,10 +921,10 @@ static const int DCH_index[KeyWord_INDEX_SIZE] = {
 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 	-1, -1, -1, -1, -1, DCH_A_D, DCH_B_C, DCH_CC, DCH_DAY, -1,
-	DCH_FX, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
+	DCH_FF1, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
 	DCH_P_M, DCH_Q, DCH_RM, DCH_SSSS, DCH_TZH, DCH_US, -1, DCH_WW, -1, DCH_Y_YYY,
 	-1, -1, -1, -1, -1, -1, -1, DCH_a_d, DCH_b_c, DCH_cc,
-	DCH_day, -1, DCH_fx, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
+	DCH_day, -1, DCH_ff1, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
 	-1, -1, DCH_p_m, DCH_q, DCH_rm, DCH_ssss, DCH_tz, DCH_us, -1, DCH_ww,
 	-1, DCH_y_yyy, -1, -1, -1, -1
 
@@ -962,6 +988,10 @@ typedef struct NUMProc
 			   *L_currency_symbol;
 } NUMProc;
 
+/* Return flags for DCH_from_char() */
+#define DCH_DATED	0x01
+#define DCH_TIMED	0x02
+#define DCH_ZONED	0x04
 
 /* ----------
  * Functions
@@ -977,7 +1007,8 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 
 static void DCH_to_char(FormatNode *node, bool is_interval,
 						TmToChar *in, char *out, Oid collid);
-static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out);
+static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
+						  bool strict);
 
 #ifdef DEBUG_TO_FROM_CHAR
 static void dump_index(const KeyWord *k, const int *index);
@@ -994,8 +1025,8 @@ static int	from_char_parse_int_len(int *dest, char **src, const int len, FormatN
 static int	from_char_parse_int(int *dest, char **src, FormatNode *node);
 static int	seq_search(char *name, const char *const *array, int type, int max, int *len);
 static int	from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
-static void do_to_timestamp(text *date_txt, text *fmt,
-							struct pg_tm *tm, fsec_t *fsec);
+static void do_to_timestamp(text *date_txt, text *fmt, bool strict,
+							struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags);
 static char *fill_str(char *str, int c, int max);
 static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
 static char *int_to_roman(int number);
@@ -2517,18 +2548,32 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 					str_numth(s, s, S_TH_TYPE(n->suffix));
 				s += strlen(s);
 				break;
-			case DCH_MS:		/* millisecond */
-				sprintf(s, "%03d", (int) (in->fsec / INT64CONST(1000)));
-				if (S_THth(n->suffix))
-					str_numth(s, s, S_TH_TYPE(n->suffix));
+#define DCH_to_char_fsec(frac_fmt, frac_val) \
+				sprintf(s, frac_fmt, (int) (frac_val)); \
+				if (S_THth(n->suffix)) \
+					str_numth(s, s, S_TH_TYPE(n->suffix)); \
 				s += strlen(s);
+			case DCH_FF1:		/* decisecond */
+				DCH_to_char_fsec("%01d", in->fsec / 100000);
+				break;
+			case DCH_FF2:		/* centisecond */
+				DCH_to_char_fsec("%02d", in->fsec / 10000);
+				break;
+			case DCH_FF3:
+			case DCH_MS:		/* millisecond */
+				DCH_to_char_fsec("%03d", in->fsec / 1000);
+				break;
+			case DCH_FF4:
+				DCH_to_char_fsec("%04d", in->fsec / 100);
 				break;
+			case DCH_FF5:
+				DCH_to_char_fsec("%05d", in->fsec / 10);
+				break;
+			case DCH_FF6:
 			case DCH_US:		/* microsecond */
-				sprintf(s, "%06d", (int) in->fsec);
-				if (S_THth(n->suffix))
-					str_numth(s, s, S_TH_TYPE(n->suffix));
-				s += strlen(s);
+				DCH_to_char_fsec("%06d", in->fsec);
 				break;
+#undef DCH_to_char_fsec
 			case DCH_SSSS:
 				sprintf(s, "%d", tm->tm_hour * SECS_PER_HOUR +
 						tm->tm_min * SECS_PER_MINUTE +
@@ -3010,13 +3055,15 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 /* ----------
  * Process a string as denoted by a list of FormatNodes.
  * The TmFromChar struct pointed to by 'out' is populated with the results.
+ * 'strict' enables error reporting on unmatched trailing characters in input or
+ * format strings patterns.
  *
  * Note: we currently don't have any to_interval() function, so there
  * is no need here for INVALID_FOR_INTERVAL checks.
  * ----------
  */
 static void
-DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
+DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 {
 	FormatNode *n;
 	char	   *s;
@@ -3153,8 +3200,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 
 				SKIP_THth(s, n->suffix);
 				break;
+			case DCH_FF1:
+			case DCH_FF2:
+			case DCH_FF3:
+			case DCH_FF4:
+			case DCH_FF5:
+			case DCH_FF6:
+				out->ff = n->key->id - DCH_FF1 + 1;
+				/* fall through */
 			case DCH_US:		/* microsecond */
-				len = from_char_parse_int_len(&out->us, &s, 6, n);
+				len = from_char_parse_int_len(&out->us, &s,
+											  n->key->id == DCH_US ? 6 :
+											  out->ff, n);
 
 				out->us *= len == 1 ? 100000 :
 					len == 2 ? 10000 :
@@ -3380,6 +3437,23 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 			}
 		}
 	}
+
+	if (strict)
+	{
+		if (n->type != NODE_TYPE_END)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+					 errmsg("input string is too short for datetime format")));
+
+		while (*s != '\0' && isspace((unsigned char) *s))
+			s++;
+
+		if (*s != '\0')
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+					 errmsg("trailing characters remain in input string after "
+							"datetime format")));
+	}
 }
 
 /*
@@ -3400,6 +3474,109 @@ DCH_prevent_counter_overflow(void)
 	}
 }
 
+/* Get mask of date/time/zone components present in format nodes. */
+static int
+DCH_datetime_type(FormatNode *node)
+{
+	FormatNode *n;
+	int			flags = 0;
+
+	for (n = node; n->type != NODE_TYPE_END; n++)
+	{
+		if (n->type != NODE_TYPE_ACTION)
+			continue;
+
+		switch (n->key->id)
+		{
+			case DCH_FX:
+				break;
+			case DCH_A_M:
+			case DCH_P_M:
+			case DCH_a_m:
+			case DCH_p_m:
+			case DCH_AM:
+			case DCH_PM:
+			case DCH_am:
+			case DCH_pm:
+			case DCH_HH:
+			case DCH_HH12:
+			case DCH_HH24:
+			case DCH_MI:
+			case DCH_SS:
+			case DCH_MS:		/* millisecond */
+			case DCH_US:		/* microsecond */
+			case DCH_FF1:
+			case DCH_FF2:
+			case DCH_FF3:
+			case DCH_FF4:
+			case DCH_FF5:
+			case DCH_FF6:
+			case DCH_SSSS:
+				flags |= DCH_TIMED;
+				break;
+			case DCH_tz:
+			case DCH_TZ:
+			case DCH_OF:
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("formatting field \"%s\" is only supported in to_char",
+								n->key->name)));
+				flags |= DCH_ZONED;
+				break;
+			case DCH_TZH:
+			case DCH_TZM:
+				flags |= DCH_ZONED;
+				break;
+			case DCH_A_D:
+			case DCH_B_C:
+			case DCH_a_d:
+			case DCH_b_c:
+			case DCH_AD:
+			case DCH_BC:
+			case DCH_ad:
+			case DCH_bc:
+			case DCH_MONTH:
+			case DCH_Month:
+			case DCH_month:
+			case DCH_MON:
+			case DCH_Mon:
+			case DCH_mon:
+			case DCH_MM:
+			case DCH_DAY:
+			case DCH_Day:
+			case DCH_day:
+			case DCH_DY:
+			case DCH_Dy:
+			case DCH_dy:
+			case DCH_DDD:
+			case DCH_IDDD:
+			case DCH_DD:
+			case DCH_D:
+			case DCH_ID:
+			case DCH_WW:
+			case DCH_Q:
+			case DCH_CC:
+			case DCH_Y_YYY:
+			case DCH_YYYY:
+			case DCH_IYYY:
+			case DCH_YYY:
+			case DCH_IYY:
+			case DCH_YY:
+			case DCH_IY:
+			case DCH_Y:
+			case DCH_I:
+			case DCH_RM:
+			case DCH_rm:
+			case DCH_W:
+			case DCH_J:
+				flags |= DCH_DATED;
+				break;
+		}
+	}
+
+	return flags;
+}
+
 /* select a DCHCacheEntry to hold the given format picture */
 static DCHCacheEntry *
 DCH_cache_getnew(const char *str)
@@ -3688,8 +3865,9 @@ to_timestamp(PG_FUNCTION_ARGS)
 	int			tz;
 	struct pg_tm tm;
 	fsec_t		fsec;
+	int			fprec;
 
-	do_to_timestamp(date_txt, fmt, &tm, &fsec);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL);
 
 	/* Use the specified time zone, if any. */
 	if (tm.tm_zone)
@@ -3707,6 +3885,10 @@ to_timestamp(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 				 errmsg("timestamp out of range")));
 
+	/* Use the specified fractional precision, if any. */
+	if (fprec)
+		AdjustTimestampForTypmod(&result, fprec);
+
 	PG_RETURN_TIMESTAMP(result);
 }
 
@@ -3724,7 +3906,7 @@ to_date(PG_FUNCTION_ARGS)
 	struct pg_tm tm;
 	fsec_t		fsec;
 
-	do_to_timestamp(date_txt, fmt, &tm, &fsec);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL);
 
 	/* Prevent overflow in Julian-day routines */
 	if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3745,11 +3927,171 @@ to_date(PG_FUNCTION_ARGS)
 	PG_RETURN_DATEADT(result);
 }
 
+/*
+ * Make datetime type from 'date_txt' which is formated at argument 'fmt'.
+ * Actual datatype (returned in 'typid', 'typmod') is determined by
+ * presence of date/time/zone components in the format string.
+ *
+ * Default time-zone for tz types is specified with 'tzname'.  If 'tzname' is
+ * NULL and the input string does not contain zone components then "missing tz"
+ * error is thrown.
+ */
+Datum
+parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
+			   int32 *typmod, int *tz)
+{
+	struct pg_tm tm;
+	fsec_t		fsec;
+	int			fprec = 0;
+	int			flags;
+
+	do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags);
+
+	*typmod = fprec ? fprec : -1;	/* fractional part precision */
+
+	if (flags & DCH_DATED)
+	{
+		if (flags & DCH_TIMED)
+		{
+			if (flags & DCH_ZONED)
+			{
+				TimestampTz result;
+
+				if (tm.tm_zone)
+				{
+					int			dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
+
+					if (dterr)
+						DateTimeParseError(dterr, text_to_cstring(date_txt), "timestamptz");
+				}
+				else
+				{
+					if (*tz == PG_INT32_MIN)
+						ereport(ERROR,
+								(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								 errmsg("missing time-zone in input string for type timestamptz")));
+				}
+
+				if (tm2timestamp(&tm, fsec, tz, &result) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("timestamptz out of range")));
+
+				AdjustTimestampForTypmod(&result, *typmod);
+
+				*typid = TIMESTAMPTZOID;
+				return TimestampTzGetDatum(result);
+			}
+			else
+			{
+				Timestamp	result;
+
+				if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("timestamp out of range")));
+
+				AdjustTimestampForTypmod(&result, *typmod);
+
+				*typid = TIMESTAMPOID;
+				return TimestampGetDatum(result);
+			}
+		}
+		else
+		{
+			if (flags & DCH_ZONED)
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+						 errmsg("datetime format is zoned but not timed")));
+			}
+			else
+			{
+				DateADT		result;
+
+				/* Prevent overflow in Julian-day routines */
+				if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("date out of range: \"%s\"",
+									text_to_cstring(date_txt))));
+
+				result = 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(result))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("date out of range: \"%s\"",
+									text_to_cstring(date_txt))));
+
+				*typid = DATEOID;
+				return DateADTGetDatum(result);
+			}
+		}
+	}
+	else if (flags & DCH_TIMED)
+	{
+		if (flags & DCH_ZONED)
+		{
+			TimeTzADT  *result = palloc(sizeof(TimeTzADT));
+
+			if (tm.tm_zone)
+			{
+				int			dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
+
+				if (dterr)
+					DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz");
+			}
+			else
+			{
+				if (*tz == PG_INT32_MIN)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							 errmsg("missing time-zone in input string for type timetz")));
+			}
+
+			if (tm2timetz(&tm, fsec, *tz, result) != 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("timetz out of range")));
+
+			AdjustTimeForTypmod(&result->time, *typmod);
+
+			*typid = TIMETZOID;
+			return TimeTzADTPGetDatum(result);
+		}
+		else
+		{
+			TimeADT		result;
+
+			if (tm2time(&tm, fsec, &result) != 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("time out of range")));
+
+			AdjustTimeForTypmod(&result, *typmod);
+
+			*typid = TIMEOID;
+			return TimeADTGetDatum(result);
+		}
+	}
+	else
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+				 errmsg("datetime format is not dated and not timed")));
+	}
+
+	return (Datum) 0;
+}
+
 /*
  * do_to_timestamp: shared code for to_timestamp and to_date
  *
  * Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm
- * and fractional seconds.
+ * and fractional seconds and fractional precision.
  *
  * We parse 'fmt' into a list of FormatNodes, which is then passed to
  * DCH_from_char to populate a TmFromChar with the parsed contents of
@@ -3757,10 +4099,15 @@ to_date(PG_FUNCTION_ARGS)
  *
  * The TmFromChar is then analysed and converted into the final results in
  * struct 'tm' and 'fsec'.
+ *
+ * Bit mask of date/time/zone components found in 'fmt' is returned in 'flags'.
+ *
+ * 'strict' enables error reporting on unmatched trailing characters in input or
+ * format strings patterns.
  */
 static void
-do_to_timestamp(text *date_txt, text *fmt,
-				struct pg_tm *tm, fsec_t *fsec)
+do_to_timestamp(text *date_txt, text *fmt, bool strict,
+				struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags)
 {
 	FormatNode *format;
 	TmFromChar	tmfc;
@@ -3813,9 +4160,13 @@ do_to_timestamp(text *date_txt, text *fmt,
 		/* dump_index(DCH_keywords, DCH_index); */
 #endif
 
-		DCH_from_char(format, date_str, &tmfc);
+		DCH_from_char(format, date_str, &tmfc, strict);
 
 		pfree(fmt_str);
+
+		if (flags)
+			*flags = DCH_datetime_type(format);
+
 		if (!incache)
 			pfree(format);
 	}
@@ -3997,6 +4348,8 @@ do_to_timestamp(text *date_txt, text *fmt,
 		*fsec += tmfc.ms * 1000;
 	if (tmfc.us)
 		*fsec += tmfc.us;
+	if (fprec)
+		*fprec = tmfc.ff;		/* fractional precision, if specified */
 
 	/* Range-check date fields according to bit mask computed above */
 	if (fmask != 0)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 5861ffbbc97..2931bd58c0d 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -70,7 +70,6 @@ typedef struct
 
 static TimeOffset time2t(const int hour, const int min, const int sec, const fsec_t fsec);
 static Timestamp dt2local(Timestamp dt, int timezone);
-static void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
 static void AdjustIntervalForTypmod(Interval *interval, int32 typmod);
 static TimestampTz timestamp2timestamptz(Timestamp timestamp);
 static Timestamp timestamptz2timestamp(TimestampTz timestamp);
@@ -333,7 +332,7 @@ timestamp_scale(PG_FUNCTION_ARGS)
  * AdjustTimestampForTypmod --- round off a timestamp to suit given typmod
  * Works for either timestamp or timestamptz.
  */
-static void
+void
 AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
 {
 	static const int64 TimestampScales[MAX_TIMESTAMP_PRECISION + 1] = {
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index bec129aff1c..bd15bfa5bb0 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -76,5 +76,8 @@ extern TimeTzADT *GetSQLCurrentTime(int32 typmod);
 extern TimeADT GetSQLLocalTime(int32 typmod);
 extern int	time2tm(TimeADT time, struct pg_tm *tm, fsec_t *fsec);
 extern int	timetz2tm(TimeTzADT *time, struct pg_tm *tm, fsec_t *fsec, int *tzp);
+extern int	tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
+extern int	tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
+extern void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
 
 #endif							/* DATE_H */
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index b8a199cdded..0cafdd26538 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -336,4 +336,6 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
 												   int n);
 extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
 
+extern void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
+
 #endif							/* DATETIME_H */
diff --git a/src/include/utils/formatting.h b/src/include/utils/formatting.h
index 5b275dc9850..a390302e89c 100644
--- a/src/include/utils/formatting.h
+++ b/src/include/utils/formatting.h
@@ -28,4 +28,7 @@ extern char *asc_tolower(const char *buff, size_t nbytes);
 extern char *asc_toupper(const char *buff, size_t nbytes);
 extern char *asc_initcap(const char *buff, size_t nbytes);
 
+extern Datum parse_datetime(text *date_txt, text *fmt, bool strict,
+							Oid *typid, int32 *typmod, int *tz);
+
 #endif
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index b2b45773339..74ecb7c10e6 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -2786,6 +2786,85 @@ SELECT to_timestamp('2011-12-18 11:38 20',     'YYYY-MM-DD HH12:MI TZM');
  Sun Dec 18 03:18:00 2011 PST
 (1 row)
 
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |         to_timestamp         
+---+------------------------------
+ 1 | Fri Nov 02 12:34:56 2018 PDT
+ 2 | Fri Nov 02 12:34:56 2018 PDT
+ 3 | Fri Nov 02 12:34:56 2018 PDT
+ 4 | Fri Nov 02 12:34:56 2018 PDT
+ 5 | Fri Nov 02 12:34:56 2018 PDT
+ 6 | Fri Nov 02 12:34:56 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |          to_timestamp          
+---+--------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.1 2018 PDT
+ 3 | Fri Nov 02 12:34:56.1 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |          to_timestamp           
+---+---------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.12 2018 PDT
+ 4 | Fri Nov 02 12:34:56.12 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |           to_timestamp           
+---+----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.123 2018 PDT
+ 5 | Fri Nov 02 12:34:56.123 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |           to_timestamp            
+---+-----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1234 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |            to_timestamp            
+---+------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12345 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12345 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |            to_timestamp             
+---+-------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12346 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123456 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ERROR:  date/time field value out of range: "2018-11-02 12:34:56.123456789"
 --
 -- Check handling of multiple spaces in format and/or input
 --
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index 715680e3302..f772b07d5a4 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -1584,6 +1584,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
             | 2001 1 1 1 1 1 1
 (65 rows)
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamp),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+ to_char_12 |                              to_char                               
+------------+--------------------------------------------------------------------
+            | 0 00 000 0000 00000 000000  0 00 000 0000 00000 000000  000 000000
+            | 7 78 780 7800 78000 780000  7 78 780 7800 78000 780000  780 780000
+            | 7 78 789 7890 78901 789010  7 78 789 7890 78901 789010  789 789010
+            | 7 78 789 7890 78901 789012  7 78 789 7890 78901 789012  789 789012
+(4 rows)
+
 -- timestamp numeric fields constructor
 SELECT make_timestamp(2014,12,28,6,30,45.887);
         make_timestamp        
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 5551fa6610e..2d6a71ca64b 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -1704,6 +1704,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
             | 2001 1 1 1 1 1 1
 (66 rows)
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamptz),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+ to_char_12 |                              to_char                               
+------------+--------------------------------------------------------------------
+            | 0 00 000 0000 00000 000000  0 00 000 0000 00000 000000  000 000000
+            | 7 78 780 7800 78000 780000  7 78 780 7800 78000 780000  780 780000
+            | 7 78 789 7890 78901 789010  7 78 789 7890 78901 789010  789 789010
+            | 7 78 789 7890 78901 789012  7 78 789 7890 78901 789012  789 789012
+(4 rows)
+
 -- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
 SET timezone = '00:00';
 SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index e356dd563ee..3c8580397ac 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -402,6 +402,15 @@ SELECT to_timestamp('2011-12-18 11:38 +05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
 SELECT to_timestamp('2011-12-18 11:38 -05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
 SELECT to_timestamp('2011-12-18 11:38 20',     'YYYY-MM-DD HH12:MI TZM');
 
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+
 --
 -- Check handling of multiple spaces in format and/or input
 --
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index 031b22bc3c1..329987f7eaa 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -224,5 +224,13 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
 SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
    FROM TIMESTAMP_TBL;
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamp),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+
 -- timestamp numeric fields constructor
 SELECT make_timestamp(2014,12,28,6,30,45.887);
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index 28c76d6b72c..f5fee639a01 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -248,6 +248,14 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
 SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
    FROM TIMESTAMPTZ_TBL;
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamptz),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+
 -- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
 SET timezone = '00:00';
 SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
0003-error-suppression-for-datetime-3.patchapplication/octet-stream; name=0003-error-suppression-for-datetime-3.patchDownload
commit a9450d6e691c97f5c817f2ed84fd3b53a83ee165
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Mon May 27 10:29:07 2019 +0300

    Error suppression support for upcoming jsonpath .datetime() method
    
    Add support of error suppression in some date and time manipulation functions
    as it's required for jsonpath .datetime() method support.  This commit doesn't
    use PG_TRY()/PG_CATCH() in order to implement that.  Instead, it provides
    internal versions of date and time functions used, which support error
    suppression.

diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 9e291b5c7bc..1456414e399 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -550,13 +550,15 @@ date_mii(PG_FUNCTION_ARGS)
 	PG_RETURN_DATEADT(result);
 }
 
+
 /*
- * Internal routines for promoting date to timestamp and timestamp with
- * time zone
+ * Promote date to timestamp.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero returned.
  */
-
-static Timestamp
-date2timestamp(DateADT dateVal)
+Timestamp
+date2timestamp_opt_error(DateADT dateVal, bool *have_error)
 {
 	Timestamp	result;
 
@@ -572,9 +574,19 @@ date2timestamp(DateADT dateVal)
 		 * boundary need be checked for overflow.
 		 */
 		if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("date out of range for timestamp")));
+		{
+			if (have_error)
+			{
+				*have_error = true;
+				return (Timestamp) 0;
+			}
+			else
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("date out of range for timestamp")));
+			}
+		}
 
 		/* date is days since 2000, timestamp is microseconds since same... */
 		result = dateVal * USECS_PER_DAY;
@@ -583,8 +595,23 @@ date2timestamp(DateADT dateVal)
 	return result;
 }
 
+/*
+ * Single-argument version of date2timestamp_opt_error().
+ */
 static TimestampTz
-date2timestamptz(DateADT dateVal)
+date2timestamp(DateADT dateVal)
+{
+	return date2timestamp_opt_error(dateVal, NULL);
+}
+
+/*
+ * Promote date to timestamp with time zone.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero returned.
+ */
+TimestampTz
+date2timestamptz_opt_error(DateADT dateVal, bool *have_error)
 {
 	TimestampTz result;
 	struct pg_tm tt,
@@ -603,9 +630,19 @@ date2timestamptz(DateADT dateVal)
 		 * boundary need be checked for overflow.
 		 */
 		if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("date out of range for timestamp")));
+		{
+			if (have_error)
+			{
+				*have_error = true;
+				return (TimestampTz) 0;
+			}
+			else
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("date out of range for timestamp")));
+			}
+		}
 
 		j2date(dateVal + POSTGRES_EPOCH_JDATE,
 			   &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday));
@@ -621,14 +658,33 @@ date2timestamptz(DateADT dateVal)
 		 * of time zone, check for allowed timestamp range after adding tz.
 		 */
 		if (!IS_VALID_TIMESTAMP(result))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("date out of range for timestamp")));
+		{
+			if (have_error)
+			{
+				*have_error = true;
+				return (TimestampTz) 0;
+			}
+			else
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("date out of range for timestamp")));
+			}
+		}
 	}
 
 	return result;
 }
 
+/*
+ * Single-argument version of date2timestamptz_opt_error().
+ */
+static TimestampTz
+date2timestamptz(DateADT dateVal)
+{
+	return date2timestamptz_opt_error(dateVal, NULL);
+}
+
 /*
  * date2timestamp_no_overflow
  *
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index fe6aa91b2e9..33ac72ef572 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -98,6 +98,43 @@
 #include "utils/numeric.h"
 #include "utils/pg_locale.h"
 
+/* ----------
+ * Convinience macros for error handling
+ * ----------
+ *
+ * Two macros below helps handling errors in functions, which takes
+ * 'bool *have_error' argument.  When this argument is not NULL, it's expected
+ * that function will suppress ereports when possible.  Instead it should
+ * return some default value and set *have_error flag.
+ *
+ * RETURN_ERROR() macro intended to wrap ereport() calls.  When have_error
+ * argument is provided, then instead of ereport'ing we set *have_error flag
+ * and go to on_error label.  It's supposed that jump resources will be freed
+ * and some 'default' value returned.
+ *
+ * CHECK_ERROR() jumps on_error label when *have_error flag is defined and set.
+ * It's supposed to be used for immediate exit from the function on error
+ * after call of another function with 'bool *have_error' argument.
+ */
+#define RETURN_ERROR(throw_error) \
+do { \
+	if (have_error) \
+	{ \
+		*have_error = true; \
+		goto on_error; \
+	} \
+	else \
+	{ \
+		throw_error; \
+	} \
+} while (0)
+
+#define CHECK_ERROR \
+do { \
+	if (have_error && *have_error) \
+		goto on_error; \
+} while (0)
+
 /* ----------
  * Routines type
  * ----------
@@ -1008,7 +1045,7 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 static void DCH_to_char(FormatNode *node, bool is_interval,
 						TmToChar *in, char *out, Oid collid);
 static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
-						  bool strict);
+						  bool strict, bool *have_error);
 
 #ifdef DEBUG_TO_FROM_CHAR
 static void dump_index(const KeyWord *k, const int *index);
@@ -1019,14 +1056,21 @@ static const char *get_th(char *num, int type);
 static char *str_numth(char *dest, char *num, int type);
 static int	adjust_partial_year_to_2020(int year);
 static int	strspace_len(char *str);
-static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode);
-static void from_char_set_int(int *dest, const int value, const FormatNode *node);
-static int	from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node);
-static int	from_char_parse_int(int *dest, char **src, FormatNode *node);
+static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode,
+							   bool *have_error);
+static void from_char_set_int(int *dest, const int value, const FormatNode *node,
+							  bool *have_error);
+static int	from_char_parse_int_len(int *dest, char **src, const int len,
+									FormatNode *node, bool *have_error);
+static int	from_char_parse_int(int *dest, char **src, FormatNode *node,
+								bool *have_error);
 static int	seq_search(char *name, const char *const *array, int type, int max, int *len);
-static int	from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
+static int	from_char_seq_search(int *dest, char **src,
+								 const char *const *array, int type, int max,
+								 FormatNode *node, bool *have_error);
 static void do_to_timestamp(text *date_txt, text *fmt, bool strict,
-							struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags);
+							struct pg_tm *tm, fsec_t *fsec, int *fprec,
+							int *flags, bool *have_error);
 static char *fill_str(char *str, int c, int max);
 static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
 static char *int_to_roman(int number);
@@ -2202,21 +2246,26 @@ strspace_len(char *str)
  *
  * Puke if the date mode has already been set, and the caller attempts to set
  * it to a conflicting mode.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
-from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode)
+from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode, bool *have_error)
 {
 	if (mode != FROM_CHAR_DATE_NONE)
 	{
 		if (tmfc->mode == FROM_CHAR_DATE_NONE)
 			tmfc->mode = mode;
 		else if (tmfc->mode != mode)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("invalid combination of date conventions"),
-					 errhint("Do not mix Gregorian and ISO week date "
-							 "conventions in a formatting template.")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("invalid combination of date conventions"),
+								  errhint("Do not mix Gregorian and ISO week date "
+										  "conventions in a formatting template."))));
 	}
+
+on_error:
+	return;
 }
 
 /*
@@ -2224,18 +2273,25 @@ from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode)
  *
  * Puke if the destination integer has previously been set to some other
  * non-zero value.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
-from_char_set_int(int *dest, const int value, const FormatNode *node)
+from_char_set_int(int *dest, const int value, const FormatNode *node,
+				  bool *have_error)
 {
 	if (*dest != 0 && *dest != value)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("conflicting values for \"%s\" field in formatting string",
-						node->key->name),
-				 errdetail("This value contradicts a previous setting for "
-						   "the same field type.")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("conflicting values for \"%s\" field in "
+									 "formatting string",
+									 node->key->name),
+							  errdetail("This value contradicts a previous setting "
+										"for the same field type."))));
 	*dest = value;
+
+on_error:
+	return;
 }
 
 /*
@@ -2257,9 +2313,13 @@ from_char_set_int(int *dest, const int value, const FormatNode *node)
  * Note that from_char_parse_int() provides a more convenient wrapper where
  * the length of the field is the same as the length of the format keyword (as
  * with DD and MI).
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and -1 is returned.
  */
 static int
-from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
+from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node,
+						bool *have_error)
 {
 	long		result;
 	char		copy[DCH_MAX_ITEM_SIZ + 1];
@@ -2292,51 +2352,60 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
 		char	   *last;
 
 		if (used < len)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("source string too short for \"%s\" formatting field",
-							node->key->name),
-					 errdetail("Field requires %d characters, but only %d "
-							   "remain.",
-							   len, used),
-					 errhint("If your source string is not fixed-width, try "
-							 "using the \"FM\" modifier.")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("source string too short for \"%s\" "
+										 "formatting field",
+										 node->key->name),
+								  errdetail("Field requires %d characters, "
+											"but only %d remain.",
+											len, used),
+								  errhint("If your source string is not fixed-width, "
+										  "try using the \"FM\" modifier."))));
 
 		errno = 0;
 		result = strtol(copy, &last, 10);
 		used = last - copy;
 
 		if (used > 0 && used < len)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("invalid value \"%s\" for \"%s\"",
-							copy, node->key->name),
-					 errdetail("Field requires %d characters, but only %d "
-							   "could be parsed.", len, used),
-					 errhint("If your source string is not fixed-width, try "
-							 "using the \"FM\" modifier.")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("invalid value \"%s\" for \"%s\"",
+										 copy, node->key->name),
+								  errdetail("Field requires %d characters, "
+											"but only %d could be parsed.",
+											len, used),
+								  errhint("If your source string is not fixed-width, "
+										  "try using the \"FM\" modifier."))));
 
 		*src += used;
 	}
 
 	if (*src == init)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("invalid value \"%s\" for \"%s\"",
-						copy, node->key->name),
-				 errdetail("Value must be an integer.")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("invalid value \"%s\" for \"%s\"",
+									 copy, node->key->name),
+							  errdetail("Value must be an integer."))));
 
 	if (errno == ERANGE || result < INT_MIN || result > INT_MAX)
-		ereport(ERROR,
-				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-				 errmsg("value for \"%s\" in source string is out of range",
-						node->key->name),
-				 errdetail("Value must be in the range %d to %d.",
-						   INT_MIN, INT_MAX)));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							  errmsg("value for \"%s\" in source string is out of range",
+									 node->key->name),
+							  errdetail("Value must be in the range %d to %d.",
+										INT_MIN, INT_MAX))));
 
 	if (dest != NULL)
-		from_char_set_int(dest, (int) result, node);
+	{
+		from_char_set_int(dest, (int) result, node, have_error);
+		CHECK_ERROR;
+	}
+
 	return *src - init;
+
+on_error:
+	return -1;
 }
 
 /*
@@ -2349,9 +2418,9 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
  * required length explicitly.
  */
 static int
-from_char_parse_int(int *dest, char **src, FormatNode *node)
+from_char_parse_int(int *dest, char **src, FormatNode *node, bool *have_error)
 {
-	return from_char_parse_int_len(dest, src, node->key->len, node);
+	return from_char_parse_int_len(dest, src, node->key->len, node, have_error);
 }
 
 /* ----------
@@ -2434,11 +2503,12 @@ seq_search(char *name, const char *const *array, int type, int max, int *len)
  * pointed to by 'dest', advance 'src' to the end of the part of the string
  * which matched, and return the number of characters consumed.
  *
- * If the string doesn't match, throw an error.
+ * If the string doesn't match, throw an error if 'have_error' is NULL,
+ * otherwise set '*have_error' and return -1.
  */
 static int
-from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max,
-					 FormatNode *node)
+from_char_seq_search(int *dest, char **src, const char *const *array, int type,
+					 int max, FormatNode *node, bool *have_error)
 {
 	int			len;
 
@@ -2450,15 +2520,18 @@ from_char_seq_search(int *dest, char **src, const char *const *array, int type,
 		Assert(max <= DCH_MAX_ITEM_SIZ);
 		strlcpy(copy, *src, max + 1);
 
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("invalid value \"%s\" for \"%s\"",
-						copy, node->key->name),
-				 errdetail("The given value did not match any of the allowed "
-						   "values for this field.")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("invalid value \"%s\" for \"%s\"",
+									 copy, node->key->name),
+							  errdetail("The given value did not match any of "
+										"the allowed values for this field."))));
 	}
 	*src += len;
 	return len;
+
+on_error:
+	return -1;
 }
 
 /* ----------
@@ -3060,10 +3133,13 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
  *
  * Note: we currently don't have any to_interval() function, so there
  * is no need here for INVALID_FOR_INTERVAL checks.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  * ----------
  */
 static void
-DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
+DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict,
+			  bool *have_error)
 {
 	FormatNode *n;
 	char	   *s;
@@ -3146,7 +3222,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 			continue;
 		}
 
-		from_char_set_mode(out, n->key->date_mode);
+		from_char_set_mode(out, n->key->date_mode, have_error);
+		CHECK_ERROR;
 
 		switch (n->key->id)
 		{
@@ -3158,8 +3235,10 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 			case DCH_a_m:
 			case DCH_p_m:
 				from_char_seq_search(&value, &s, ampm_strings_long,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->pm, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->pm, value % 2, n, have_error);
+				CHECK_ERROR;
 				out->clock = CLOCK_12_HOUR;
 				break;
 			case DCH_AM:
@@ -3167,30 +3246,37 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 			case DCH_am:
 			case DCH_pm:
 				from_char_seq_search(&value, &s, ampm_strings,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->pm, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->pm, value % 2, n, have_error);
+				CHECK_ERROR;
 				out->clock = CLOCK_12_HOUR;
 				break;
 			case DCH_HH:
 			case DCH_HH12:
-				from_char_parse_int_len(&out->hh, &s, 2, n);
+				from_char_parse_int_len(&out->hh, &s, 2, n, have_error);
+				CHECK_ERROR;
 				out->clock = CLOCK_12_HOUR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_HH24:
-				from_char_parse_int_len(&out->hh, &s, 2, n);
+				from_char_parse_int_len(&out->hh, &s, 2, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_MI:
-				from_char_parse_int(&out->mi, &s, n);
+				from_char_parse_int(&out->mi, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_SS:
-				from_char_parse_int(&out->ss, &s, n);
+				from_char_parse_int(&out->ss, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_MS:		/* millisecond */
-				len = from_char_parse_int_len(&out->ms, &s, 3, n);
+				len = from_char_parse_int_len(&out->ms, &s, 3, n, have_error);
+				CHECK_ERROR;
 
 				/*
 				 * 25 is 0.25 and 250 is 0.25 too; 025 is 0.025 and not 0.25
@@ -3211,7 +3297,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 			case DCH_US:		/* microsecond */
 				len = from_char_parse_int_len(&out->us, &s,
 											  n->key->id == DCH_US ? 6 :
-											  out->ff, n);
+											  out->ff, n, have_error);
+				CHECK_ERROR;
 
 				out->us *= len == 1 ? 100000 :
 					len == 2 ? 10000 :
@@ -3222,16 +3309,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_SSSS:
-				from_char_parse_int(&out->ssss, &s, n);
+				from_char_parse_int(&out->ssss, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_tz:
 			case DCH_TZ:
 			case DCH_OF:
-				ereport(ERROR,
-						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						 errmsg("formatting field \"%s\" is only supported in to_char",
-								n->key->name)));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									  errmsg("formatting field \"%s\" is only supported in to_char",
+											 n->key->name))));
+				CHECK_ERROR;
 				break;
 			case DCH_TZH:
 
@@ -3255,82 +3344,102 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 						out->tzsign = +1;
 				}
 
-				from_char_parse_int_len(&out->tzh, &s, 2, n);
+				from_char_parse_int_len(&out->tzh, &s, 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_TZM:
 				/* assign positive timezone sign if TZH was not seen before */
 				if (!out->tzsign)
 					out->tzsign = +1;
-				from_char_parse_int_len(&out->tzm, &s, 2, n);
+				from_char_parse_int_len(&out->tzm, &s, 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_A_D:
 			case DCH_B_C:
 			case DCH_a_d:
 			case DCH_b_c:
 				from_char_seq_search(&value, &s, adbc_strings_long,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->bc, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->bc, value % 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_AD:
 			case DCH_BC:
 			case DCH_ad:
 			case DCH_bc:
 				from_char_seq_search(&value, &s, adbc_strings,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->bc, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->bc, value % 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_MONTH:
 			case DCH_Month:
 			case DCH_month:
 				from_char_seq_search(&value, &s, months_full, ONE_UPPER,
-									 MAX_MONTH_LEN, n);
-				from_char_set_int(&out->mm, value + 1, n);
+									 MAX_MONTH_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, value + 1, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_MON:
 			case DCH_Mon:
 			case DCH_mon:
 				from_char_seq_search(&value, &s, months, ONE_UPPER,
-									 MAX_MON_LEN, n);
-				from_char_set_int(&out->mm, value + 1, n);
+									 MAX_MON_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, value + 1, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_MM:
-				from_char_parse_int(&out->mm, &s, n);
+				from_char_parse_int(&out->mm, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_DAY:
 			case DCH_Day:
 			case DCH_day:
 				from_char_seq_search(&value, &s, days, ONE_UPPER,
-									 MAX_DAY_LEN, n);
-				from_char_set_int(&out->d, value, n);
+									 MAX_DAY_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->d, value, n, have_error);
+				CHECK_ERROR;
 				out->d++;
 				break;
 			case DCH_DY:
 			case DCH_Dy:
 			case DCH_dy:
 				from_char_seq_search(&value, &s, days, ONE_UPPER,
-									 MAX_DY_LEN, n);
-				from_char_set_int(&out->d, value, n);
+									 MAX_DY_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->d, value, n, have_error);
+				CHECK_ERROR;
 				out->d++;
 				break;
 			case DCH_DDD:
-				from_char_parse_int(&out->ddd, &s, n);
+				from_char_parse_int(&out->ddd, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_IDDD:
-				from_char_parse_int_len(&out->ddd, &s, 3, n);
+				from_char_parse_int_len(&out->ddd, &s, 3, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_DD:
-				from_char_parse_int(&out->dd, &s, n);
+				from_char_parse_int(&out->dd, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_D:
-				from_char_parse_int(&out->d, &s, n);
+				from_char_parse_int(&out->d, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_ID:
-				from_char_parse_int_len(&out->d, &s, 1, n);
+				from_char_parse_int_len(&out->d, &s, 1, n, have_error);
+				CHECK_ERROR;
 				/* Shift numbering to match Gregorian where Sunday = 1 */
 				if (++out->d > 7)
 					out->d = 1;
@@ -3338,7 +3447,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 				break;
 			case DCH_WW:
 			case DCH_IW:
-				from_char_parse_int(&out->ww, &s, n);
+				from_char_parse_int(&out->ww, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Q:
@@ -3353,11 +3463,13 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 				 * We still parse the source string for an integer, but it
 				 * isn't stored anywhere in 'out'.
 				 */
-				from_char_parse_int((int *) NULL, &s, n);
+				from_char_parse_int((int *) NULL, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_CC:
-				from_char_parse_int(&out->cc, &s, n);
+				from_char_parse_int(&out->cc, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Y_YYY:
@@ -3369,11 +3481,12 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 
 					matched = sscanf(s, "%d,%03d%n", &millennia, &years, &nch);
 					if (matched < 2)
-						ereport(ERROR,
-								(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-								 errmsg("invalid input string for \"Y,YYY\"")));
+						RETURN_ERROR(ereport(ERROR,
+											 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+											  errmsg("invalid input string for \"Y,YYY\""))));
 					years += (millennia * 1000);
-					from_char_set_int(&out->year, years, n);
+					from_char_set_int(&out->year, years, n, have_error);
+					CHECK_ERROR;
 					out->yysz = 4;
 					s += nch;
 					SKIP_THth(s, n->suffix);
@@ -3381,47 +3494,62 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 				break;
 			case DCH_YYYY:
 			case DCH_IYYY:
-				from_char_parse_int(&out->year, &s, n);
+				from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
 				out->yysz = 4;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_YYY:
 			case DCH_IYY:
-				if (from_char_parse_int(&out->year, &s, n) < 4)
+				len = from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
+				if (len < 4)
 					out->year = adjust_partial_year_to_2020(out->year);
 				out->yysz = 3;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_YY:
 			case DCH_IY:
-				if (from_char_parse_int(&out->year, &s, n) < 4)
+				len = from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
+				if (len < 4)
 					out->year = adjust_partial_year_to_2020(out->year);
 				out->yysz = 2;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Y:
 			case DCH_I:
-				if (from_char_parse_int(&out->year, &s, n) < 4)
+				len = from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
+				if (len < 4)
 					out->year = adjust_partial_year_to_2020(out->year);
 				out->yysz = 1;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_RM:
 				from_char_seq_search(&value, &s, rm_months_upper,
-									 ALL_UPPER, MAX_RM_LEN, n);
-				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n);
+									 ALL_UPPER, MAX_RM_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value,
+								  n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_rm:
 				from_char_seq_search(&value, &s, rm_months_lower,
-									 ALL_LOWER, MAX_RM_LEN, n);
-				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n);
+									 ALL_LOWER, MAX_RM_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value,
+								  n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_W:
-				from_char_parse_int(&out->w, &s, n);
+				from_char_parse_int(&out->w, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_J:
-				from_char_parse_int(&out->j, &s, n);
+				from_char_parse_int(&out->j, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 		}
@@ -3441,19 +3569,22 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 	if (strict)
 	{
 		if (n->type != NODE_TYPE_END)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("input string is too short for datetime format")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("input string is too short for datetime format"))));
 
 		while (*s != '\0' && isspace((unsigned char) *s))
 			s++;
 
 		if (*s != '\0')
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("trailing characters remain in input string after "
-							"datetime format")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("trailing characters remain in input string "
+										 "after datetime format"))));
 	}
+
+on_error:
+	return;
 }
 
 /*
@@ -3474,9 +3605,13 @@ DCH_prevent_counter_overflow(void)
 	}
 }
 
-/* Get mask of date/time/zone components present in format nodes. */
+/*
+ * Get mask of date/time/zone components present in format nodes.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
+ */
 static int
-DCH_datetime_type(FormatNode *node)
+DCH_datetime_type(FormatNode *node, bool *have_error)
 {
 	FormatNode *n;
 	int			flags = 0;
@@ -3517,10 +3652,10 @@ DCH_datetime_type(FormatNode *node)
 			case DCH_tz:
 			case DCH_TZ:
 			case DCH_OF:
-				ereport(ERROR,
-						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						 errmsg("formatting field \"%s\" is only supported in to_char",
-								n->key->name)));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									  errmsg("formatting field \"%s\" is only supported in to_char",
+											 n->key->name))));
 				flags |= DCH_ZONED;
 				break;
 			case DCH_TZH:
@@ -3574,6 +3709,7 @@ DCH_datetime_type(FormatNode *node)
 		}
 	}
 
+on_error:
 	return flags;
 }
 
@@ -3867,7 +4003,7 @@ to_timestamp(PG_FUNCTION_ARGS)
 	fsec_t		fsec;
 	int			fprec;
 
-	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL, NULL);
 
 	/* Use the specified time zone, if any. */
 	if (tm.tm_zone)
@@ -3906,7 +4042,7 @@ to_date(PG_FUNCTION_ARGS)
 	struct pg_tm tm;
 	fsec_t		fsec;
 
-	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL, NULL);
 
 	/* Prevent overflow in Julian-day routines */
 	if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3935,17 +4071,21 @@ to_date(PG_FUNCTION_ARGS)
  * Default time-zone for tz types is specified with 'tzname'.  If 'tzname' is
  * NULL and the input string does not contain zone components then "missing tz"
  * error is thrown.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero value is returned.
  */
 Datum
 parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
-			   int32 *typmod, int *tz)
+			   int32 *typmod, int *tz, bool *have_error)
 {
 	struct pg_tm tm;
 	fsec_t		fsec;
 	int			fprec = 0;
 	int			flags;
 
-	do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags);
+	do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags, have_error);
+	CHECK_ERROR;
 
 	*typmod = fprec ? fprec : -1;	/* fractional part precision */
 
@@ -3966,16 +4106,22 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 				}
 				else
 				{
-					if (*tz == PG_INT32_MIN)
-						ereport(ERROR,
-								(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-								 errmsg("missing time-zone in input string for type timestamptz")));
+					/*
+					 * Time zone is present in format string, but not in input
+					 * string.  Assuming do_to_timestamp() triggers no error
+					 * this should be possible only in non-strict case.
+					 */
+					Assert(!strict);
+
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+										  errmsg("missing time-zone in input string for type timestamptz"))));
 				}
 
 				if (tm2timestamp(&tm, fsec, tz, &result) != 0)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("timestamptz out of range")));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("timestamptz out of range"))));
 
 				AdjustTimestampForTypmod(&result, *typmod);
 
@@ -3987,9 +4133,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 				Timestamp	result;
 
 				if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("timestamp out of range")));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("timestamp out of range"))));
 
 				AdjustTimestampForTypmod(&result, *typmod);
 
@@ -4001,9 +4147,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 		{
 			if (flags & DCH_ZONED)
 			{
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-						 errmsg("datetime format is zoned but not timed")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+									  errmsg("datetime format is zoned but not timed"))));
 			}
 			else
 			{
@@ -4011,20 +4157,20 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 
 				/* Prevent overflow in Julian-day routines */
 				if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("date out of range: \"%s\"",
-									text_to_cstring(date_txt))));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("date out of range: \"%s\"",
+												 text_to_cstring(date_txt)))));
 
 				result = 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(result))
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("date out of range: \"%s\"",
-									text_to_cstring(date_txt))));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("date out of range: \"%s\"",
+												 text_to_cstring(date_txt)))));
 
 				*typid = DATEOID;
 				return DateADTGetDatum(result);
@@ -4042,20 +4188,26 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 				int			dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
 
 				if (dterr)
-					DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz");
+					RETURN_ERROR(DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz"));
 			}
 			else
 			{
-				if (*tz == PG_INT32_MIN)
-					ereport(ERROR,
-							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-							 errmsg("missing time-zone in input string for type timetz")));
+				/*
+				 * Time zone is present in format string, but not in input
+				 * string.  Assuming do_to_timestamp() triggers no error this
+				 * should be possible only in non-strict case.
+				 */
+				Assert(!strict);
+
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+									  errmsg("missing time-zone in input string for type timetz"))));
 			}
 
 			if (tm2timetz(&tm, fsec, *tz, result) != 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-						 errmsg("timetz out of range")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+									  errmsg("timetz out of range"))));
 
 			AdjustTimeForTypmod(&result->time, *typmod);
 
@@ -4067,9 +4219,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 			TimeADT		result;
 
 			if (tm2time(&tm, fsec, &result) != 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-						 errmsg("time out of range")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+									  errmsg("time out of range"))));
 
 			AdjustTimeForTypmod(&result, *typmod);
 
@@ -4079,11 +4231,12 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 	}
 	else
 	{
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("datetime format is not dated and not timed")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("datetime format is not dated and not timed"))));
 	}
 
+on_error:
 	return (Datum) 0;
 }
 
@@ -4104,16 +4257,19 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
  *
  * 'strict' enables error reporting on unmatched trailing characters in input or
  * format strings patterns.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
-do_to_timestamp(text *date_txt, text *fmt, bool strict,
-				struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags)
+do_to_timestamp(text *date_txt, text *fmt, bool strict, struct pg_tm *tm,
+				fsec_t *fsec, int *fprec, int *flags, bool *have_error)
 {
-	FormatNode *format;
+	FormatNode *format = NULL;
 	TmFromChar	tmfc;
 	int			fmt_len;
 	char	   *date_str;
 	int			fmask;
+	bool		incache = false;
 
 	date_str = text_to_cstring(date_txt);
 
@@ -4127,7 +4283,6 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 	if (fmt_len)
 	{
 		char	   *fmt_str;
-		bool		incache;
 
 		fmt_str = text_to_cstring(fmt);
 
@@ -4137,8 +4292,6 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 			 * Allocate new memory if format picture is bigger than static
 			 * cache and do not use cache (call parser always)
 			 */
-			incache = false;
-
 			format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode));
 
 			parse_format(format, fmt_str, DCH_keywords,
@@ -4160,15 +4313,21 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 		/* dump_index(DCH_keywords, DCH_index); */
 #endif
 
-		DCH_from_char(format, date_str, &tmfc, strict);
+		DCH_from_char(format, date_str, &tmfc, strict, have_error);
+		CHECK_ERROR;
 
 		pfree(fmt_str);
 
 		if (flags)
-			*flags = DCH_datetime_type(format);
+			*flags = DCH_datetime_type(format, have_error);
 
 		if (!incache)
+		{
 			pfree(format);
+			format = NULL;
+		}
+
+		CHECK_ERROR;
 	}
 
 	DEBUG_TMFC(&tmfc);
@@ -4197,11 +4356,13 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 	if (tmfc.clock == CLOCK_12_HOUR)
 	{
 		if (tm->tm_hour < 1 || tm->tm_hour > HOURS_PER_DAY / 2)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("hour \"%d\" is invalid for the 12-hour clock",
-							tm->tm_hour),
-					 errhint("Use the 24-hour clock, or give an hour between 1 and 12.")));
+		{
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("hour \"%d\" is invalid for the 12-hour clock",
+										 tm->tm_hour),
+								  errhint("Use the 24-hour clock, or give an hour between 1 and 12."))));
+		}
 
 		if (tmfc.pm && tm->tm_hour < HOURS_PER_DAY / 2)
 			tm->tm_hour += HOURS_PER_DAY / 2;
@@ -4305,9 +4466,11 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 		 */
 
 		if (!tm->tm_year && !tmfc.bc)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("cannot calculate day of year without year information")));
+		{
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("cannot calculate day of year without year information"))));
+		}
 
 		if (tmfc.mode == FROM_CHAR_DATE_ISOWEEK)
 		{
@@ -4364,7 +4527,7 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 			 * said DTERR_MD_FIELD_OVERFLOW, because we don't want to print an
 			 * irrelevant hint about datestyle.
 			 */
-			DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp");
+			RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp"));
 		}
 	}
 
@@ -4373,7 +4536,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 		tm->tm_min < 0 || tm->tm_min >= MINS_PER_HOUR ||
 		tm->tm_sec < 0 || tm->tm_sec >= SECS_PER_MINUTE ||
 		*fsec < INT64CONST(0) || *fsec >= USECS_PER_SEC)
-		DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp");
+	{
+		RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp"));
+	}
 
 	/* Save parsed time-zone into tm->tm_zone if it was specified */
 	if (tmfc.tzsign)
@@ -4382,7 +4547,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 
 		if (tmfc.tzh < 0 || tmfc.tzh > MAX_TZDISP_HOUR ||
 			tmfc.tzm < 0 || tmfc.tzm >= MINS_PER_HOUR)
-			DateTimeParseError(DTERR_TZDISP_OVERFLOW, date_str, "timestamp");
+		{
+			RETURN_ERROR(DateTimeParseError(DTERR_TZDISP_OVERFLOW, date_str, "timestamp"));
+		}
 
 		tz = psprintf("%c%02d:%02d",
 					  tmfc.tzsign > 0 ? '+' : '-', tmfc.tzh, tmfc.tzm);
@@ -4392,6 +4559,11 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 
 	DEBUG_TM(tm);
 
+on_error:
+
+	if (format && !incache)
+		pfree(format);
+
 	pfree(date_str);
 }
 
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 2931bd58c0d..80b8d656f2f 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -329,11 +329,11 @@ timestamp_scale(PG_FUNCTION_ARGS)
 }
 
 /*
- * AdjustTimestampForTypmod --- round off a timestamp to suit given typmod
+ * AdjustTimestampForTypmodError --- round off a timestamp to suit given typmod
  * Works for either timestamp or timestamptz.
  */
-void
-AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
+bool
+AdjustTimestampForTypmodError(Timestamp *time, int32 typmod, bool *error)
 {
 	static const int64 TimestampScales[MAX_TIMESTAMP_PRECISION + 1] = {
 		INT64CONST(1000000),
@@ -359,10 +359,18 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
 		&& (typmod != -1) && (typmod != MAX_TIMESTAMP_PRECISION))
 	{
 		if (typmod < 0 || typmod > MAX_TIMESTAMP_PRECISION)
+		{
+			if (error)
+			{
+				*error = true;
+				return false;
+			}
+
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("timestamp(%d) precision must be between %d and %d",
 							typmod, 0, MAX_TIMESTAMP_PRECISION)));
+		}
 
 		if (*time >= INT64CONST(0))
 		{
@@ -375,8 +383,15 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
 					  * TimestampScales[typmod]);
 		}
 	}
+
+	return true;
 }
 
+void
+AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
+{
+	(void) AdjustTimestampForTypmodError(time, typmod, NULL);
+}
 
 /* timestamptz_in()
  * Convert a string to internal form.
@@ -5172,8 +5187,15 @@ timestamp_timestamptz(PG_FUNCTION_ARGS)
 	PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
 }
 
-static TimestampTz
-timestamp2timestamptz(Timestamp timestamp)
+/*
+ * Convert timestamp to timestamp with time zone.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero returned.
+ */
+
+TimestampTz
+timestamp2timestamptz_opt_error(Timestamp timestamp, bool *have_error)
 {
 	TimestampTz result;
 	struct pg_tm tt,
@@ -5182,23 +5204,33 @@ timestamp2timestamptz(Timestamp timestamp)
 	int			tz;
 
 	if (TIMESTAMP_NOT_FINITE(timestamp))
-		result = timestamp;
-	else
-	{
-		if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("timestamp out of range")));
+		return timestamp;
 
+	if (!timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL))
+	{
 		tz = DetermineTimeZoneOffset(tm, session_timezone);
 
-		if (tm2timestamp(tm, fsec, &tz, &result) != 0)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("timestamp out of range")));
+		if (!tm2timestamp(tm, fsec, &tz, &result))
+			return result;
 	}
 
-	return result;
+	if (have_error)
+		*have_error = true;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+				 errmsg("timestamp out of range")));
+
+	return 0;
+}
+
+/*
+ * Single-argument version of timestamp2timestamptz_opt_error().
+ */
+static TimestampTz
+timestamp2timestamptz(Timestamp timestamp)
+{
+	return timestamp2timestamptz_opt_error(timestamp, NULL);
 }
 
 /* timestamptz_timestamp()
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index bd15bfa5bb0..c29f13aaf04 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -70,6 +70,8 @@ typedef struct
 /* date.c */
 extern int32 anytime_typmod_check(bool istz, int32 typmod);
 extern double date2timestamp_no_overflow(DateADT dateVal);
+extern Timestamp date2timestamp_opt_error(DateADT dateVal, bool *have_error);
+extern TimestampTz date2timestamptz_opt_error(DateADT dateVal, bool *have_error);
 extern void EncodeSpecialDate(DateADT dt, char *str);
 extern DateADT GetSQLCurrentDate(void);
 extern TimeTzADT *GetSQLCurrentTime(int32 typmod);
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index 0cafdd26538..5ebf336071b 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -337,5 +337,7 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
 extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
 
 extern void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
+extern bool AdjustTimestampForTypmodError(Timestamp *time, int32 typmod,
+										  bool *error);
 
 #endif							/* DATETIME_H */
diff --git a/src/include/utils/formatting.h b/src/include/utils/formatting.h
index a390302e89c..1a2e2a9caa8 100644
--- a/src/include/utils/formatting.h
+++ b/src/include/utils/formatting.h
@@ -29,6 +29,7 @@ extern char *asc_toupper(const char *buff, size_t nbytes);
 extern char *asc_initcap(const char *buff, size_t nbytes);
 
 extern Datum parse_datetime(text *date_txt, text *fmt, bool strict,
-							Oid *typid, int32 *typmod, int *tz);
+							Oid *typid, int32 *typmod, int *tz,
+							bool *have_error);
 
 #endif
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index ea16190ec3d..e884d4405f5 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -97,6 +97,9 @@ extern int	timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
 /* timestamp comparison works for timestamptz also */
 #define timestamptz_cmp_internal(dt1,dt2)	timestamp_cmp_internal(dt1, dt2)
 
+extern TimestampTz timestamp2timestamptz_opt_error(Timestamp timestamp,
+												   bool *have_error);
+
 extern int	isoweek2j(int year, int week);
 extern void isoweek2date(int woy, int *year, int *mon, int *mday);
 extern void isoweekdate2date(int isoweek, int wday, int *year, int *mon, int *mday);
0001-datetime-in-JsonbValue-3.patchapplication/octet-stream; name=0001-datetime-in-JsonbValue-3.patchDownload
commit 0845588d81414ba96f2e6ed6003164794800366b
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Thu May 16 21:48:26 2019 +0300

    Allow datetime values in JsonbValue
    
    SQL/JSON standard allows manipulation with datetime values.  So, it appears to
    be convinient to allow datetime values to be represented in JsonbValue struct.
    These datetime values are allowed for temporary representation only.  During
    serialization datetime values are converted into strings.
    
    SQL/JSON requires writing timestamps with timezone in the same timezone offset
    as they were parsed.  This is why we allow storage of timezone offset in
    JsonbValue struct.  For the same reason timezone offset argument is added to
    JsonEncodeDateTime() function.

diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index 26d293709aa..7440f77cbe5 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -1506,7 +1506,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 			{
 				char		buf[MAXDATELEN + 1];
 
-				JsonEncodeDateTime(buf, val, DATEOID);
+				JsonEncodeDateTime(buf, val, DATEOID, NULL);
 				appendStringInfo(result, "\"%s\"", buf);
 			}
 			break;
@@ -1514,7 +1514,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 			{
 				char		buf[MAXDATELEN + 1];
 
-				JsonEncodeDateTime(buf, val, TIMESTAMPOID);
+				JsonEncodeDateTime(buf, val, TIMESTAMPOID, NULL);
 				appendStringInfo(result, "\"%s\"", buf);
 			}
 			break;
@@ -1522,7 +1522,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 			{
 				char		buf[MAXDATELEN + 1];
 
-				JsonEncodeDateTime(buf, val, TIMESTAMPTZOID);
+				JsonEncodeDateTime(buf, val, TIMESTAMPTZOID, NULL);
 				appendStringInfo(result, "\"%s\"", buf);
 			}
 			break;
@@ -1550,10 +1550,11 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 
 /*
  * Encode 'value' of datetime type 'typid' into JSON string in ISO format using
- * optionally preallocated buffer 'buf'.
+ * optionally preallocated buffer 'buf'.  Optional 'tzp' determines time-zone
+ * offset (in seconds) in which we want to show timestamptz.
  */
 char *
-JsonEncodeDateTime(char *buf, Datum value, Oid typid)
+JsonEncodeDateTime(char *buf, Datum value, Oid typid, const int *tzp)
 {
 	if (!buf)
 		buf = palloc(MAXDATELEN + 1);
@@ -1630,11 +1631,30 @@ JsonEncodeDateTime(char *buf, Datum value, Oid typid)
 				const char *tzn = NULL;
 
 				timestamp = DatumGetTimestampTz(value);
+
+				/*
+				 * If time-zone is specified, we apply a time-zone shift,
+				 * convert timestamptz to pg_tm as if it was without
+				 * time-zone, and then use specified time-zone for encoding
+				 * timestamp into a string.
+				 */
+				if (tzp)
+				{
+					tz = *tzp;
+					timestamp -= (TimestampTz) tz * USECS_PER_SEC;
+				}
+
 				/* Same as timestamptz_out(), but forcing DateStyle */
 				if (TIMESTAMP_NOT_FINITE(timestamp))
 					EncodeSpecialTimestamp(timestamp, buf);
-				else if (timestamp2tm(timestamp, &tz, &tm, &fsec, &tzn, NULL) == 0)
+				else if (timestamp2tm(timestamp, tzp ? NULL : &tz, &tm, &fsec,
+									  tzp ? NULL : &tzn, NULL) == 0)
+				{
+					if (tzp)
+						tm.tm_isdst = 1;	/* set time-zone presence flag */
+
 					EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf);
+				}
 				else
 					ereport(ERROR,
 							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 69f41ab4556..74b4bbe44c6 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -206,6 +206,24 @@ JsonbTypeName(JsonbValue *jbv)
 			return "boolean";
 		case jbvNull:
 			return "null";
+		case jbvDatetime:
+			switch (jbv->val.datetime.typid)
+			{
+				case DATEOID:
+					return "date";
+				case TIMEOID:
+					return "time without time zone";
+				case TIMETZOID:
+					return "time with time zone";
+				case TIMESTAMPOID:
+					return "timestamp without time zone";
+				case TIMESTAMPTZOID:
+					return "timestamp with time zone";
+				default:
+					elog(ERROR, "unrecognized jsonb value datetime type: %d",
+						 jbv->val.datetime.typid);
+			}
+			return "unknown";
 		default:
 			elog(ERROR, "unrecognized jsonb value type: %d", jbv->type);
 			return "unknown";
@@ -805,17 +823,20 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result,
 				break;
 			case JSONBTYPE_DATE:
 				jb.type = jbvString;
-				jb.val.string.val = JsonEncodeDateTime(NULL, val, DATEOID);
+				jb.val.string.val = JsonEncodeDateTime(NULL, val,
+													   DATEOID, NULL);
 				jb.val.string.len = strlen(jb.val.string.val);
 				break;
 			case JSONBTYPE_TIMESTAMP:
 				jb.type = jbvString;
-				jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPOID);
+				jb.val.string.val = JsonEncodeDateTime(NULL, val,
+													   TIMESTAMPOID, NULL);
 				jb.val.string.len = strlen(jb.val.string.val);
 				break;
 			case JSONBTYPE_TIMESTAMPTZ:
 				jb.type = jbvString;
-				jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPTZOID);
+				jb.val.string.val = JsonEncodeDateTime(NULL, val,
+													   TIMESTAMPTZOID, NULL);
 				jb.val.string.len = strlen(jb.val.string.val);
 				break;
 			case JSONBTYPE_JSONCAST:
diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index 7969f6f5843..cb2bd872cf5 100644
--- a/src/backend/utils/adt/jsonb_util.c
+++ b/src/backend/utils/adt/jsonb_util.c
@@ -14,9 +14,12 @@
 #include "postgres.h"
 
 #include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
 #include "miscadmin.h"
 #include "utils/builtins.h"
+#include "utils/datetime.h"
 #include "utils/hashutils.h"
+#include "utils/jsonapi.h"
 #include "utils/jsonb.h"
 #include "utils/memutils.h"
 #include "utils/varlena.h"
@@ -241,6 +244,7 @@ compareJsonbContainers(JsonbContainer *a, JsonbContainer *b)
 							res = (va.val.object.nPairs > vb.val.object.nPairs) ? 1 : -1;
 						break;
 					case jbvBinary:
+					case jbvDatetime:
 						elog(ERROR, "unexpected jbvBinary value");
 				}
 			}
@@ -1749,6 +1753,22 @@ convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal)
 				JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE;
 			break;
 
+		case jbvDatetime:
+			{
+				char		buf[MAXDATELEN + 1];
+				size_t		len;
+
+				JsonEncodeDateTime(buf,
+								   scalarVal->val.datetime.value,
+								   scalarVal->val.datetime.typid,
+								   &scalarVal->val.datetime.tz);
+				len = strlen(buf);
+				appendToBuffer(buffer, buf, len);
+
+				*jentry = JENTRY_ISSTRING | len;
+			}
+			break;
+
 		default:
 			elog(ERROR, "invalid jsonb scalar type");
 	}
diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h
index 5f4d479a7b2..b707b09ce31 100644
--- a/src/include/utils/jsonapi.h
+++ b/src/include/utils/jsonapi.h
@@ -161,6 +161,7 @@ extern Jsonb *transform_jsonb_string_values(Jsonb *jsonb, void *action_state,
 extern text *transform_json_string_values(text *json, void *action_state,
 										  JsonTransformStringValuesAction transform_action);
 
-extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid);
+extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid,
+								const int *tzp);
 
 #endif							/* JSONAPI_H */
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index 2fe7d32fec2..ec66d150263 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -241,7 +241,15 @@ enum jbvType
 	jbvArray = 0x10,
 	jbvObject,
 	/* Binary (i.e. struct Jsonb) jbvArray/jbvObject */
-	jbvBinary
+	jbvBinary,
+
+	/*
+	 * Virtual types.
+	 *
+	 * These types are used only for in-memory JSON processing and serialized
+	 * into JSON strings when outputted to json/jsonb.
+	 */
+	jbvDatetime = 0x20,
 };
 
 /*
@@ -282,11 +290,21 @@ struct JsonbValue
 			int			len;
 			JsonbContainer *data;
 		}			binary;		/* Array or object, in on-disk format */
+
+		struct
+		{
+			Datum		value;
+			Oid			typid;
+			int32		typmod;
+			int			tz;		/* Numeric time zone, in seconds for
+								 * TimestampTz data type */
+		}			datetime;
 	}			val;
 };
 
-#define IsAJsonbScalar(jsonbval)	((jsonbval)->type >= jbvNull && \
-									 (jsonbval)->type <= jbvBool)
+#define IsAJsonbScalar(jsonbval)	(((jsonbval)->type >= jbvNull && \
+									  (jsonbval)->type <= jbvBool) || \
+									  (jsonbval)->type == jbvDatetime)
 
 /*
  * Key/value pair within an Object.
0004-implement-jsonpath-datetime-3.patchapplication/octet-stream; name=0004-implement-jsonpath-datetime-3.patchDownload
commit a3830e499d7d9b7f2718654c8588969b5137b269
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Mon May 27 10:33:09 2019 +0300

    Implement jsonpath .datetime() method
    
    This commit implements jsonpath .datetime() method as it's specified in
    SQL/JSON standard.  There are no-argument and single-argument versions of
    this method.  No-argument version selects first of ISO datetime formats
    matching input string.  Single-argument version accepts template string as
    its argument.
    
    Additionally to .datetime() method itself this commit also implements
    comparison ability of resulting date and time values.  There is some difficulty
    because exising jsonb_path_*() functions are immutable, while comparison of
    timezoned and non-timezoned types involves current timezone.  At first, current
    timezone could be changes in session.  Moreover, timezones themselves are not
    immutable and could be updated.  This is why we let existing immutable functions
    throw errors on such non-immutable comparison.  In the same time this commit
    provides jsonb_path_*_tz() functions which are stable and support operations
    involving timezones.

diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ea4c85e3959..423ae4fdc8a 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1287,6 +1287,46 @@ LANGUAGE INTERNAL
 STRICT IMMUTABLE PARALLEL SAFE
 AS 'jsonb_path_query_first';
 
+CREATE OR REPLACE FUNCTION
+  jsonb_path_exists_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                    silent boolean DEFAULT false)
+RETURNS boolean
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_exists_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_match_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                   silent boolean DEFAULT false)
+RETURNS boolean
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_match_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_query_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                   silent boolean DEFAULT false)
+RETURNS SETOF jsonb
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_query_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_query_array_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                         silent boolean DEFAULT false)
+RETURNS jsonb
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_query_array_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_query_first_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                         silent boolean DEFAULT false)
+RETURNS jsonb
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_query_first_tz';
+
 --
 -- The default permissions for functions mean that anyone can execute them.
 -- A number of functions shouldn't be executable by just anyone, but rather
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 87ae60e490f..65c047acc86 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -337,12 +337,14 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
 		case jpiPlus:
 		case jpiMinus:
 		case jpiExists:
+		case jpiDatetime:
 			{
 				int32		arg = reserveSpaceForItemPointer(buf);
 
-				chld = flattenJsonPathParseItem(buf, item->value.arg,
-												nestingLevel + argNestingLevel,
-												insideArraySubscript);
+				chld = !item->value.arg ? pos :
+					flattenJsonPathParseItem(buf, item->value.arg,
+											 nestingLevel + argNestingLevel,
+											 insideArraySubscript);
 				*(int32 *) (buf->data + arg) = chld - pos;
 			}
 			break;
@@ -692,6 +694,15 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
 		case jpiDouble:
 			appendBinaryStringInfo(buf, ".double()", 9);
 			break;
+		case jpiDatetime:
+			appendBinaryStringInfo(buf, ".datetime(", 10);
+			if (v->content.arg)
+			{
+				jspGetArg(v, &elem);
+				printJsonPathItem(buf, &elem, false, false);
+			}
+			appendStringInfoChar(buf, ')');
+			break;
 		case jpiKeyValue:
 			appendBinaryStringInfo(buf, ".keyvalue()", 11);
 			break;
@@ -754,6 +765,8 @@ jspOperationName(JsonPathItemType type)
 			return "floor";
 		case jpiCeiling:
 			return "ceiling";
+		case jpiDatetime:
+			return "datetime";
 		default:
 			elog(ERROR, "unrecognized jsonpath item type: %d", type);
 			return NULL;
@@ -889,6 +902,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
 		case jpiPlus:
 		case jpiMinus:
 		case jpiFilter:
+		case jpiDatetime:
 			read_int32(v->content.arg, base, pos);
 			break;
 		case jpiIndexArray:
@@ -913,7 +927,8 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a)
 		   v->type == jpiIsUnknown ||
 		   v->type == jpiExists ||
 		   v->type == jpiPlus ||
-		   v->type == jpiMinus);
+		   v->type == jpiMinus ||
+		   v->type == jpiDatetime);
 
 	jspInitByBuffer(a, v->base, v->content.arg);
 }
@@ -961,6 +976,7 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
 			   v->type == jpiFloor ||
 			   v->type == jpiCeiling ||
 			   v->type == jpiDouble ||
+			   v->type == jpiDatetime ||
 			   v->type == jpiKeyValue ||
 			   v->type == jpiStartsWith);
 
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index 6bf4dcaec33..196f1901b4e 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -66,6 +66,7 @@
 #include "miscadmin.h"
 #include "regex/regex.h"
 #include "utils/builtins.h"
+#include "utils/datetime.h"
 #include "utils/datum.h"
 #include "utils/formatting.h"
 #include "utils/float.h"
@@ -107,6 +108,7 @@ typedef struct JsonPathExecContext
 										 * ignored */
 	bool		throwErrors;	/* with "false" all suppressible errors are
 								 * suppressed */
+	bool		useTz;
 } JsonPathExecContext;
 
 /* Context for LIKE_REGEX execution. */
@@ -172,7 +174,8 @@ typedef JsonPathBool (*JsonPathPredicateCallback) (JsonPathItem *jsp,
 typedef Numeric (*BinaryArithmFunc) (Numeric num1, Numeric num2, bool *error);
 
 static JsonPathExecResult executeJsonPath(JsonPath *path, Jsonb *vars,
-										  Jsonb *json, bool throwErrors, JsonValueList *result);
+										  Jsonb *json, bool throwErrors,
+										  JsonValueList *result, bool useTz);
 static JsonPathExecResult executeItem(JsonPathExecContext *cxt,
 									  JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
 static JsonPathExecResult executeItemOptUnwrapTarget(JsonPathExecContext *cxt,
@@ -215,6 +218,8 @@ static JsonPathBool executeLikeRegex(JsonPathItem *jsp, JsonbValue *str,
 static JsonPathExecResult executeNumericItemMethod(JsonPathExecContext *cxt,
 												   JsonPathItem *jsp, JsonbValue *jb, bool unwrap, PGFunction func,
 												   JsonValueList *found);
+static JsonPathExecResult executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
+												JsonbValue *jb, JsonValueList *found);
 static JsonPathExecResult executeKeyValueMethod(JsonPathExecContext *cxt,
 												JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
 static JsonPathExecResult appendBoolResult(JsonPathExecContext *cxt,
@@ -226,7 +231,8 @@ static void getJsonPathVariable(JsonPathExecContext *cxt,
 static int	JsonbArraySize(JsonbValue *jb);
 static JsonPathBool executeComparison(JsonPathItem *cmp, JsonbValue *lv,
 									  JsonbValue *rv, void *p);
-static JsonPathBool compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2);
+static JsonPathBool compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2,
+								 bool useTz);
 static int	compareNumeric(Numeric a, Numeric b);
 static JsonbValue *copyJsonbValue(JsonbValue *src);
 static JsonPathExecResult getArrayIndex(JsonPathExecContext *cxt,
@@ -247,6 +253,8 @@ static JsonbValue *JsonbInitBinary(JsonbValue *jbv, Jsonb *jb);
 static int	JsonbType(JsonbValue *jb);
 static JsonbValue *getScalar(JsonbValue *scalar, enum jbvType type);
 static JsonbValue *wrapItemsInArray(const JsonValueList *items);
+static int	compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
+							bool useTz, bool *have_error);
 
 /****************** User interface to JsonPath executor ********************/
 
@@ -262,8 +270,8 @@ static JsonbValue *wrapItemsInArray(const JsonValueList *items);
  *		SQL/JSON.  Regarding jsonb_path_match(), this function doesn't have
  *		an analogy in SQL/JSON, so we define its behavior on our own.
  */
-Datum
-jsonb_path_exists(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_exists_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -277,7 +285,7 @@ jsonb_path_exists(PG_FUNCTION_ARGS)
 		silent = PG_GETARG_BOOL(3);
 	}
 
-	res = executeJsonPath(jp, vars, jb, !silent, NULL);
+	res = executeJsonPath(jp, vars, jb, !silent, NULL, tz);
 
 	PG_FREE_IF_COPY(jb, 0);
 	PG_FREE_IF_COPY(jp, 1);
@@ -288,6 +296,18 @@ jsonb_path_exists(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(res == jperOk);
 }
 
+Datum
+jsonb_path_exists(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_exists_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_exists_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_exists_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_exists_opr
  *		Implementation of operator "jsonb @? jsonpath" (2-argument version of
@@ -297,7 +317,7 @@ Datum
 jsonb_path_exists_opr(PG_FUNCTION_ARGS)
 {
 	/* just call the other one -- it can handle both cases */
-	return jsonb_path_exists(fcinfo);
+	return jsonb_path_exists_internal(fcinfo, false);
 }
 
 /*
@@ -305,8 +325,8 @@ jsonb_path_exists_opr(PG_FUNCTION_ARGS)
  *		Returns jsonpath predicate result item for the specified jsonb value.
  *		See jsonb_path_exists() comment for details regarding error handling.
  */
-Datum
-jsonb_path_match(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_match_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -320,7 +340,7 @@ jsonb_path_match(PG_FUNCTION_ARGS)
 		silent = PG_GETARG_BOOL(3);
 	}
 
-	(void) executeJsonPath(jp, vars, jb, !silent, &found);
+	(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 	PG_FREE_IF_COPY(jb, 0);
 	PG_FREE_IF_COPY(jp, 1);
@@ -344,6 +364,18 @@ jsonb_path_match(PG_FUNCTION_ARGS)
 	PG_RETURN_NULL();
 }
 
+Datum
+jsonb_path_match(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_match_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_match_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_match_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_match_opr
  *		Implementation of operator "jsonb @@ jsonpath" (2-argument version of
@@ -353,7 +385,7 @@ Datum
 jsonb_path_match_opr(PG_FUNCTION_ARGS)
 {
 	/* just call the other one -- it can handle both cases */
-	return jsonb_path_match(fcinfo);
+	return jsonb_path_match_internal(fcinfo, false);
 }
 
 /*
@@ -361,8 +393,8 @@ jsonb_path_match_opr(PG_FUNCTION_ARGS)
  *		Executes jsonpath for given jsonb document and returns result as
  *		rowset.
  */
-Datum
-jsonb_path_query(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_query_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	FuncCallContext *funcctx;
 	List	   *found;
@@ -386,7 +418,7 @@ jsonb_path_query(PG_FUNCTION_ARGS)
 		vars = PG_GETARG_JSONB_P_COPY(2);
 		silent = PG_GETARG_BOOL(3);
 
-		(void) executeJsonPath(jp, vars, jb, !silent, &found);
+		(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 		funcctx->user_fctx = JsonValueListGetList(&found);
 
@@ -407,13 +439,25 @@ jsonb_path_query(PG_FUNCTION_ARGS)
 	SRF_RETURN_NEXT(funcctx, JsonbPGetDatum(JsonbValueToJsonb(v)));
 }
 
+Datum
+jsonb_path_query(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_query_array
  *		Executes jsonpath for given jsonb document and returns result as
  *		jsonb array.
  */
-Datum
-jsonb_path_query_array(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_query_array_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -421,18 +465,30 @@ jsonb_path_query_array(PG_FUNCTION_ARGS)
 	Jsonb	   *vars = PG_GETARG_JSONB_P(2);
 	bool		silent = PG_GETARG_BOOL(3);
 
-	(void) executeJsonPath(jp, vars, jb, !silent, &found);
+	(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 	PG_RETURN_JSONB_P(JsonbValueToJsonb(wrapItemsInArray(&found)));
 }
 
+Datum
+jsonb_path_query_array(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_array_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_array_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_array_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_query_first
  *		Executes jsonpath for given jsonb document and returns first result
  *		item.  If there are no items, NULL returned.
  */
-Datum
-jsonb_path_query_first(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_query_first_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -440,7 +496,7 @@ jsonb_path_query_first(PG_FUNCTION_ARGS)
 	Jsonb	   *vars = PG_GETARG_JSONB_P(2);
 	bool		silent = PG_GETARG_BOOL(3);
 
-	(void) executeJsonPath(jp, vars, jb, !silent, &found);
+	(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 	if (JsonValueListLength(&found) >= 1)
 		PG_RETURN_JSONB_P(JsonbValueToJsonb(JsonValueListHead(&found)));
@@ -448,6 +504,18 @@ jsonb_path_query_first(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 }
 
+Datum
+jsonb_path_query_first(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_first_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_first_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_first_internal(fcinfo, true);
+}
+
 /********************Execute functions for JsonPath**************************/
 
 /*
@@ -471,7 +539,7 @@ jsonb_path_query_first(PG_FUNCTION_ARGS)
  */
 static JsonPathExecResult
 executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors,
-				JsonValueList *result)
+				JsonValueList *result, bool useTz)
 {
 	JsonPathExecContext cxt;
 	JsonPathExecResult res;
@@ -501,6 +569,7 @@ executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors,
 	cxt.lastGeneratedObjectId = vars ? 2 : 1;
 	cxt.innermostArraySize = -1;
 	cxt.throwErrors = throwErrors;
+	cxt.useTz = useTz;
 
 	if (jspStrictAbsenseOfErrors(&cxt) && !result)
 	{
@@ -1029,6 +1098,12 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
 			}
 			break;
 
+		case jpiDatetime:
+			if (unwrap && JsonbType(jb) == jbvArray)
+				return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
+
+			return executeDateTimeMethod(cxt, jsp, jb, found);
+
 		case jpiKeyValue:
 			if (unwrap && JsonbType(jb) == jbvArray)
 				return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
@@ -1215,7 +1290,7 @@ executeBoolItem(JsonPathExecContext *cxt, JsonPathItem *jsp,
 			jspGetLeftArg(jsp, &larg);
 			jspGetRightArg(jsp, &rarg);
 			return executePredicate(cxt, jsp, &larg, &rarg, jb, true,
-									executeComparison, NULL);
+									executeComparison, cxt);
 
 		case jpiStartsWith:		/* 'whole STARTS WITH initial' */
 			jspGetLeftArg(jsp, &larg);	/* 'whole' */
@@ -1718,6 +1793,135 @@ executeNumericItemMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
 	return executeNextItem(cxt, jsp, &next, jb, found, false);
 }
 
+/*
+ * Implementation of .datetime() method.
+ *
+ * Parses string as datetime value.  Particular type is determined runtime.
+ * When argument is provided then it's used as template string.  When no
+ * argument is supplied, first fitting ISO format is selected.
+ */
+static JsonPathExecResult
+executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
+					  JsonbValue *jb, JsonValueList *found)
+{
+	JsonbValue	jbvbuf;
+	Datum		value;
+	text	   *datetime;
+	Oid			typid;
+	int32		typmod = -1;
+	int			tz = 0;
+	bool		hasNext;
+	JsonPathExecResult res = jperNotFound;
+	JsonPathItem elem;
+
+	if (!(jb = getScalar(jb, jbvString)))
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+							  errmsg("invalid argument for SQL/JSON datetime function"),
+							  errdetail("jsonpath item method .%s() is applied to not a string",
+										jspOperationName(jsp->type)))));
+
+	datetime = cstring_to_text_with_len(jb->val.string.val,
+										jb->val.string.len);
+
+	if (jsp->content.arg)
+	{
+		text	   *template;
+		char	   *template_str;
+		int			template_len;
+		bool		have_error = false;
+
+		jspGetArg(jsp, &elem);
+
+		if (elem.type != jpiString)
+			elog(ERROR, "invalid jsonpath item type for .datetime() argument");
+
+		template_str = jspGetString(&elem, &template_len);
+
+		template = cstring_to_text_with_len(template_str,
+											template_len);
+
+		value = parse_datetime(datetime, template, true,
+							   &typid, &typmod, &tz,
+							   jspThrowErrors(cxt) ? NULL : &have_error);
+
+		if (have_error)
+			res = jperError;
+		else
+			res = jperOk;
+	}
+	else
+	{
+		/* Try to recognize one of ISO formats. */
+		static const char *fmt_str[] =
+		{
+			"yyyy-mm-dd HH24:MI:SS TZH:TZM",
+			"yyyy-mm-dd HH24:MI:SS TZH",
+			"yyyy-mm-dd HH24:MI:SS",
+			"yyyy-mm-dd",
+			"HH24:MI:SS TZH:TZM",
+			"HH24:MI:SS TZH",
+			"HH24:MI:SS"
+		};
+
+		/* cache for format texts */
+		static text *fmt_txt[lengthof(fmt_str)] = {0};
+		int			i;
+
+		/* loop until datetime format fits */
+		for (i = 0; i < lengthof(fmt_str); i++)
+		{
+			bool		have_error = false;
+
+			if (!fmt_txt[i])
+			{
+				MemoryContext oldcxt =
+				MemoryContextSwitchTo(TopMemoryContext);
+
+				fmt_txt[i] = cstring_to_text(fmt_str[i]);
+				MemoryContextSwitchTo(oldcxt);
+			}
+
+			value = parse_datetime(datetime, fmt_txt[i], true,
+								   &typid, &typmod, &tz,
+								   &have_error);
+
+			if (!have_error)
+			{
+				res = jperOk;
+				break;
+			}
+		}
+
+		if (res == jperNotFound)
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+								  errmsg("invalid argument for SQL/JSON datetime function"),
+								  errdetail("unrecognized datetime format"),
+								  errhint("use datetime template argument for explicit format specification"))));
+	}
+
+	pfree(datetime);
+
+	if (jperIsError(res))
+		return res;
+
+	hasNext = jspGetNext(jsp, &elem);
+
+	if (!hasNext && !found)
+		return res;
+
+	jb = hasNext ? &jbvbuf : palloc(sizeof(*jb));
+
+	jb->type = jbvDatetime;
+	jb->val.datetime.value = value;
+	jb->val.datetime.typid = typid;
+	jb->val.datetime.typmod = typmod;
+	jb->val.datetime.tz = tz;
+
+	return executeNextItem(cxt, jsp, &elem, jb, found, hasNext);
+}
+
 /*
  * Implementation of .keyvalue() method.
  *
@@ -1978,14 +2182,16 @@ JsonbArraySize(JsonbValue *jb)
 static JsonPathBool
 executeComparison(JsonPathItem *cmp, JsonbValue *lv, JsonbValue *rv, void *p)
 {
-	return compareItems(cmp->type, lv, rv);
+	JsonPathExecContext *cxt = (JsonPathExecContext *) p;
+
+	return compareItems(cmp->type, lv, rv, cxt->useTz);
 }
 
 /*
  * Compare two SQL/JSON items using comparison operation 'op'.
  */
 static JsonPathBool
-compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2)
+compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2, bool useTz)
 {
 	int			cmp;
 	bool		res;
@@ -2027,6 +2233,21 @@ compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2)
 							 jb2->val.string.val, jb2->val.string.len,
 							 DEFAULT_COLLATION_OID);
 			break;
+		case jbvDatetime:
+			{
+				bool		have_error = false;
+
+				cmp = compareDatetime(jb1->val.datetime.value,
+									  jb1->val.datetime.typid,
+									  jb2->val.datetime.value,
+									  jb2->val.datetime.typid,
+									  useTz,
+									  &have_error);
+
+				if (have_error)
+					return jpbUnknown;
+			}
+			break;
 
 		case jbvBinary:
 		case jbvArray:
@@ -2283,3 +2504,205 @@ wrapItemsInArray(const JsonValueList *items)
 
 	return pushJsonbValue(&ps, WJB_END_ARRAY, NULL);
 }
+
+/*
+ * Cross-type comparison of two datetime SQL/JSON items.  If items are
+ * uncomparable, 'error' flag is set.
+ */
+static int
+compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
+				bool useTz, bool *have_error)
+{
+	PGFunction cmpfunc = NULL;
+
+	switch (typid1)
+	{
+		case DATEOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					cmpfunc = date_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					val1 = date2timestamp_opt_error(val1, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("can't convert value from %s to %s without timezone usage",
+										"date", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val1 = date2timestamptz_opt_error(val1, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMEOID:
+			switch (typid2)
+			{
+				case TIMEOID:
+					cmpfunc = time_cmp;
+
+					break;
+
+				case TIMETZOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("can't convert value from %s to %s without timezone usage",
+										"time", "timetz"),
+								 errhint("use *_tz() function for timezone support")));
+					val1 = DirectFunctionCall1(time_timetz, val1);
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case DATEOID:
+				case TIMESTAMPOID:
+				case TIMESTAMPTZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMETZOID:
+			switch (typid2)
+			{
+				case TIMEOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("can't convert value from %s to %s without timezone usage",
+										"time", "timetz"),
+								 errhint("use *_tz() function for timezone support")));
+					val2 = DirectFunctionCall1(time_timetz, val2);
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case TIMETZOID:
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case DATEOID:
+				case TIMESTAMPOID:
+				case TIMESTAMPTZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMESTAMPOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					val2 = date2timestamp_opt_error(val2, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("can't convert value from %s to %s without timezone usage",
+										"timestamp", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val1 = timestamp2timestamptz_opt_error(val1, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMESTAMPTZOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("can't convert value from %s to %s without timezone usage",
+										"date", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val2 = date2timestamptz_opt_error(val2, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("can't convert value from %s to %s without timezone usage",
+										"timestamp", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val2 = timestamp2timestamptz_opt_error(val2, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		default:
+			elog(ERROR, "unrecognized SQL/JSON datetime type oid: %d",
+				 typid1);
+	}
+
+	if (*have_error)
+		return 0;
+
+	if (!cmpfunc)
+		elog(ERROR, "unrecognized SQL/JSON datetime type oid: %d",
+			 typid2);
+
+	*have_error = false;
+
+	return DatumGetInt32(DirectFunctionCall2(cmpfunc, val1, val2));
+}
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
index a0a930ccf0c..912f46c682b 100644
--- a/src/backend/utils/adt/jsonpath_gram.y
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -94,12 +94,14 @@ static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr,
 %token	<str>		LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
 %token	<str>		ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P
 %token	<str>		ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P KEYVALUE_P
+%token	<str>		DATETIME_P
 
 %type	<result>	result
 
 %type	<value>		scalar_value path_primary expr array_accessor
 					any_path accessor_op key predicate delimited_predicate
 					index_elem starts_with_initial expr_or_predicate
+					datetime_template opt_datetime_template
 
 %type	<elems>		accessor_expr
 
@@ -247,9 +249,20 @@ accessor_op:
 	| array_accessor				{ $$ = $1; }
 	| '.' any_path					{ $$ = $2; }
 	| '.' method '(' ')'			{ $$ = makeItemType($2); }
+	| '.' DATETIME_P '(' opt_datetime_template ')'
+									{ $$ = makeItemUnary(jpiDatetime, $4); }
 	| '?' '(' predicate ')'			{ $$ = makeItemUnary(jpiFilter, $3); }
 	;
 
+datetime_template:
+	STRING_P						{ $$ = makeItemString(&$1); }
+	;
+
+opt_datetime_template:
+	datetime_template				{ $$ = $1; }
+	| /* EMPTY */					{ $$ = NULL; }
+	;
+
 key:
 	key_name						{ $$ = makeItemKey(&$1); }
 	;
@@ -272,6 +285,7 @@ key_name:
 	| FLOOR_P
 	| DOUBLE_P
 	| CEILING_P
+	| DATETIME_P
 	| KEYVALUE_P
 	| LAST_P
 	| STARTS_P
diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l
index 2165ffcc254..ced65edb59b 100644
--- a/src/backend/utils/adt/jsonpath_scan.l
+++ b/src/backend/utils/adt/jsonpath_scan.l
@@ -340,6 +340,7 @@ static const JsonPathKeyword keywords[] = {
 	{ 6, false,	STRICT_P,	"strict"},
 	{ 7, false,	CEILING_P,	"ceiling"},
 	{ 7, false,	UNKNOWN_P,	"unknown"},
+	{ 8, false,	DATETIME_P,	"datetime"},
 	{ 8, false,	KEYVALUE_P,	"keyvalue"},
 	{ 10,false, LIKE_REGEX_P, "like_regex"},
 };
diff --git a/src/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index 16f5ca233a9..72876533acf 100644
--- a/src/backend/utils/errcodes.txt
+++ b/src/backend/utils/errcodes.txt
@@ -207,6 +207,7 @@ Section: Class 22 - Data Exception
 2200S    E    ERRCODE_INVALID_XML_COMMENT                                    invalid_xml_comment
 2200T    E    ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION                     invalid_xml_processing_instruction
 22030    E    ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE                        duplicate_json_object_key_value
+22031    E    ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION            invalid_argument_for_json_datetime_function
 22032    E    ERRCODE_INVALID_JSON_TEXT                                      invalid_json_text
 22033    E    ERRCODE_INVALID_JSON_SUBSCRIPT                                 invalid_json_subscript
 22034    E    ERRCODE_MORE_THAN_ONE_JSON_ITEM                                more_than_one_json_item
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 87335248a03..8e4b342c778 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9279,6 +9279,28 @@
   proname => 'jsonb_path_match', prorettype => 'bool',
   proargtypes => 'jsonb jsonpath jsonb bool', prosrc => 'jsonb_path_match' },
 
+{ oid => '6015', descr => 'jsonpath exists test with timezone',
+  proname => 'jsonb_path_exists_tz', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_exists_tz' },
+{ oid => '6016', descr => 'jsonpath query with timezone',
+  proname => 'jsonb_path_query_tz', provolatile => 's',
+  prorows => '1000', proretset => 't',
+  prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_query_tz' },
+{ oid => '6017', descr => 'jsonpath query wrapped into array with timezone',
+  proname => 'jsonb_path_query_array_tz', provolatile => 's',
+  prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_query_array_tz' },
+{ oid => '6018', descr => 'jsonpath query first item with timezone',
+  proname => 'jsonb_path_query_first_tz', provolatile => 's',
+  prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_query_first_tz' },
+{ oid => '6019', descr => 'jsonpath match with timezone',
+  proname => 'jsonb_path_match_tz', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_match_tz' },
+
 { oid => '4010', descr => 'implementation of @? operator',
   proname => 'jsonb_path_exists_opr', prorettype => 'bool',
   proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_path_exists_opr' },
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 40ad5fda928..f6b17c8aa2f 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -79,6 +79,7 @@ typedef enum JsonPathItemType
 	jpiFloor,					/* .floor() item method */
 	jpiCeiling,					/* .ceiling() item method */
 	jpiDouble,					/* .double() item method */
+	jpiDatetime,				/* .datetime() item method */
 	jpiKeyValue,				/* .keyvalue() item method */
 	jpiSubscript,				/* array subscript: 'expr' or 'expr TO expr' */
 	jpiLast,					/* LAST array subscript */
diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
index 31a871af028..750802989be 100644
--- a/src/test/regress/expected/jsonb_jsonpath.out
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -1658,6 +1658,532 @@ select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ lik
  "a\b"
 (1 row)
 
+select jsonb_path_query('null', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() is applied to not a string
+select jsonb_path_query('true', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() is applied to not a string
+select jsonb_path_query('1', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() is applied to not a string
+select jsonb_path_query('[]', '$.datetime()');
+ jsonb_path_query 
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'strict $.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() is applied to not a string
+select jsonb_path_query('{}', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() is applied to not a string
+select jsonb_path_query('""', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  unrecognized datetime format
+HINT:  use datetime template argument for explicit format specification
+select jsonb_path_query('"12:34"', '$.datetime("aaa")');
+ERROR:  trailing characters remain in input string after datetime format
+select jsonb_path_query('"aaaa"', '$.datetime("HH24")');
+ERROR:  invalid value "aa" for "HH24"
+DETAIL:  Value must be an integer.
+select jsonb '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")';
+ ?column? 
+----------
+ t
+(1 row)
+
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+ jsonb_path_query 
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+ jsonb_path_query 
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+ERROR:  trailing characters remain in input string after datetime format
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+ERROR:  trailing characters remain in input string after datetime format
+select jsonb_path_query('"10-03-2017 12:34"', '       $.datetime("dd-mm-yyyy HH24:MI").type()');
+       jsonb_path_query        
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+      jsonb_path_query      
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+     jsonb_path_query     
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+   jsonb_path_query    
+-----------------------
+ "time with time zone"
+(1 row)
+
+set time zone '+00';
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+ jsonb_path_query 
+------------------
+ "12:34:00"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone '+10';
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+ jsonb_path_query 
+------------------
+ "12:34:00"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone default;
+select jsonb_path_query('"2017-03-10"', '$.datetime().type()');
+ jsonb_path_query 
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"2017-03-10"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+       jsonb_path_query        
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:34:56"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()');
+      jsonb_path_query      
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:56+03:00"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()');
+      jsonb_path_query      
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:56+03:10"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime().type()');
+     jsonb_path_query     
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "12:34:56"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()');
+   jsonb_path_query    
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "12:34:56+03:00"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()');
+   jsonb_path_query    
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "12:34:56+03:10"
+(1 row)
+
+set time zone '+00';
+-- date comparison
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR:  can't convert value from date to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR:  can't convert value from date to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR:  can't convert value from date to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10"
+ "2017-03-10T00:00:00"
+ "2017-03-10T03:00:00+03:00"
+(3 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10"
+ "2017-03-11"
+ "2017-03-10T00:00:00"
+ "2017-03-10T12:34:56"
+ "2017-03-10T03:00:00+03:00"
+(5 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-09"
+ "2017-03-10T01:02:03+04:00"
+(2 rows)
+
+-- time comparison
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+ERROR:  can't convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+ERROR:  can't convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+ERROR:  can't convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00"
+ "12:35:00+00:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00"
+ "12:36:00"
+ "12:35:00+00:00"
+(3 rows)
+
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:34:00"
+ "12:35:00+01:00"
+ "13:35:00+01:00"
+(3 rows)
+
+-- timetz comparison
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR:  can't convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR:  can't convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR:  can't convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00+01:00"
+(1 row)
+
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00+01:00"
+ "12:36:00+01:00"
+ "12:35:00-02:00"
+ "11:35:00"
+ "12:35:00"
+(5 rows)
+
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:34:00+01:00"
+ "12:35:00+02:00"
+ "10:35:00"
+(3 rows)
+
+-- timestamp comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR:  can't convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR:  can't convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR:  can't convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T13:35:00+01:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T12:36:00"
+ "2017-03-10T13:35:00+01:00"
+ "2017-03-10T12:35:00-01:00"
+ "2017-03-11"
+(5 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:34:00"
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10"
+(3 rows)
+
+-- timestamptz comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR:  can't convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR:  can't convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR:  can't convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T11:35:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+set time zone default;
 -- jsonpath operators
 SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
  jsonb_path_query 
diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
index ecdd453942b..f5d5fa4fb8d 100644
--- a/src/test/regress/expected/jsonpath.out
+++ b/src/test/regress/expected/jsonpath.out
@@ -401,6 +401,18 @@ select '$.keyvalue().key'::jsonpath;
  $.keyvalue()."key"
 (1 row)
 
+select '$.datetime()'::jsonpath;
+   jsonpath   
+--------------
+ $.datetime()
+(1 row)
+
+select '$.datetime("datetime template")'::jsonpath;
+            jsonpath             
+---------------------------------
+ $.datetime("datetime template")
+(1 row)
+
 select '$ ? (@ starts with "abc")'::jsonpath;
         jsonpath         
 -------------------------
diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
index 733fbd4e0d0..2c16182e554 100644
--- a/src/test/regress/sql/jsonb_jsonpath.sql
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -346,6 +346,178 @@ select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ lik
 select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\B$" flag "iq")');
 select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\b$" flag "")');
 
+select jsonb_path_query('null', '$.datetime()');
+select jsonb_path_query('true', '$.datetime()');
+select jsonb_path_query('1', '$.datetime()');
+select jsonb_path_query('[]', '$.datetime()');
+select jsonb_path_query('[]', 'strict $.datetime()');
+select jsonb_path_query('{}', '$.datetime()');
+select jsonb_path_query('""', '$.datetime()');
+select jsonb_path_query('"12:34"', '$.datetime("aaa")');
+select jsonb_path_query('"aaaa"', '$.datetime("HH24")');
+
+select jsonb '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")';
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+
+select jsonb_path_query('"10-03-2017 12:34"', '       $.datetime("dd-mm-yyyy HH24:MI").type()');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+
+set time zone '+00';
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone '+10';
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone default;
+
+select jsonb_path_query('"2017-03-10"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()');
+select jsonb_path_query('"12:34:56"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56"', '$.datetime()');
+select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56 +3"', '$.datetime()');
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()');
+
+set time zone '+00';
+
+-- date comparison
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+
+-- time comparison
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+
+-- timetz comparison
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+
+-- timestamp comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+
+-- timestamptz comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+
+set time zone default;
+
 -- jsonpath operators
 
 SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
index 29ea77a4858..713d32400a7 100644
--- a/src/test/regress/sql/jsonpath.sql
+++ b/src/test/regress/sql/jsonpath.sql
@@ -72,6 +72,8 @@ select '"aaa".type()'::jsonpath;
 select 'true.type()'::jsonpath;
 select '$.double().floor().ceiling().abs()'::jsonpath;
 select '$.keyvalue().key'::jsonpath;
+select '$.datetime()'::jsonpath;
+select '$.datetime("datetime template")'::jsonpath;
 
 select '$ ? (@ starts with "abc")'::jsonpath;
 select '$ ? (@ starts with $var)'::jsonpath;
#4Anastasia Lubennikova
lubennikovaav@gmail.com
In reply to: Alexander Korotkov (#3)
Re: Support for jsonpath .datetime() method

The following review has been posted through the commitfest application:
make installcheck-world: tested, passed
Implements feature: tested, passed
Spec compliant: tested, passed
Documentation: not tested

Hi,

In general, the feature looks good. It is consistent with the standard and the code around.
It definitely needs more documentation - datetime() and new jsonb_path_*_tz() functions are not documented.

Here are also minor questions on implementation and code style:

1) + case jbvDatetime:
elog(ERROR, "unexpected jbvBinary value");
We should use separate error message for jvbDatetime here.

2) + *jentry = JENTRY_ISSTRING | len;
Here we can avoid using JENTRY_ISSTRING since it defined to 0x0.
I propose to do so to be consistent with jbvString case.

3) 
+ * Default time-zone for tz types is specified with 'tzname'.  If 'tzname' is
+ * NULL and the input string does not contain zone components then "missing tz"
+ * error is thrown.
+ */
+Datum
+parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
+               int32 *typmod, int *tz)

The comment about 'tzname' is outdated.

4) Some typos:

+ * Convinience macros for error handling

* Convenience macros for error handling

+ * Two macros below helps handling errors in functions, which takes

* Two macros below help to handle errors in functions, which take

5) + * RETURN_ERROR() macro intended to wrap ereport() calls. When have_error
+ * argument is provided, then instead of ereport'ing we set *have_error flag

have_error is not a macro argument, so I suggest to rephrase this comment.

Shouldn't we pass have_error explicitly?
In case someone will change the name of the variable, this macro will work incorrectly.

6) * When no argument is supplied, first fitting ISO format is selected.
+        /* Try to recognize one of ISO formats. */
+        static const char *fmt_str[] =
+        {
+            "yyyy-mm-dd HH24:MI:SS TZH:TZM",
+            "yyyy-mm-dd HH24:MI:SS TZH",
+            "yyyy-mm-dd HH24:MI:SS",
+            "yyyy-mm-dd",
+            "HH24:MI:SS TZH:TZM",
+            "HH24:MI:SS TZH",
+            "HH24:MI:SS"
+        };

How do we choose the order of formats to check? Is it in standard?
Anyway, I think this struct needs a comment that explains that changing of order can affect end-user.

7) +		if (res == jperNotFound)
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+								  errmsg("invalid argument for SQL/JSON datetime function"),
+								  errdetail("unrecognized datetime format"),
+								  errhint("use datetime template argument for explicit format specification"))));

The hint is confusing. If I understand correctly, no-arg datetime function supports all formats,
so if parsing failed, it must be an invalid argument and providing format explicitly won't help.

The new status of this patch is: Waiting on Author

#5Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Anastasia Lubennikova (#4)
4 attachment(s)
Re: Support for jsonpath .datetime() method

Hi!

Thank you for the review!

Revised version of patch is attached.

On Mon, Jul 15, 2019 at 3:57 PM Anastasia Lubennikova
<lubennikovaav@gmail.com> wrote:

In general, the feature looks good. It is consistent with the standard and the code around.
It definitely needs more documentation - datetime() and new jsonb_path_*_tz() functions are not documented.

Documentation is added for both jsonpath .datetime() method and SQL
jsonb_path_*_tz() functions.

Here are also minor questions on implementation and code style:

1) + case jbvDatetime:
elog(ERROR, "unexpected jbvBinary value");
We should use separate error message for jvbDatetime here.

Fixed.

2) + *jentry = JENTRY_ISSTRING | len;
Here we can avoid using JENTRY_ISSTRING since it defined to 0x0.
I propose to do so to be consistent with jbvString case.

Fixed.

3)
+ * Default time-zone for tz types is specified with 'tzname'.  If 'tzname' is
+ * NULL and the input string does not contain zone components then "missing tz"
+ * error is thrown.
+ */
+Datum
+parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
+               int32 *typmod, int *tz)

The comment about 'tzname' is outdated.

Fixed.

4) Some typos:

+ * Convinience macros for error handling

* Convenience macros for error handling

+ * Two macros below helps handling errors in functions, which takes

* Two macros below help to handle errors in functions, which take

Fixed.

5) + * RETURN_ERROR() macro intended to wrap ereport() calls. When have_error
+ * argument is provided, then instead of ereport'ing we set *have_error flag

have_error is not a macro argument, so I suggest to rephrase this comment.

Shouldn't we pass have_error explicitly?
In case someone will change the name of the variable, this macro will work incorrectly.

Comment about RETURN_ERROR() is updated. RETURN_ERROR() is just
file-wide macro. So I think in this case it's ok to pass *have_error
flag implicitly for the sake of brevity.

6) * When no argument is supplied, first fitting ISO format is selected.
+        /* Try to recognize one of ISO formats. */
+        static const char *fmt_str[] =
+        {
+            "yyyy-mm-dd HH24:MI:SS TZH:TZM",
+            "yyyy-mm-dd HH24:MI:SS TZH",
+            "yyyy-mm-dd HH24:MI:SS",
+            "yyyy-mm-dd",
+            "HH24:MI:SS TZH:TZM",
+            "HH24:MI:SS TZH",
+            "HH24:MI:SS"
+        };

How do we choose the order of formats to check? Is it in standard?
Anyway, I think this struct needs a comment that explains that changing of order can affect end-user.

Yes, standard defines which order we should try datetime types (and
corresponding ISO formats). I've updated respectively array, its
comment and docs.

7) +            if (res == jperNotFound)
+                       RETURN_ERROR(ereport(ERROR,
+                                                                (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+                                                                 errmsg("invalid argument for SQL/JSON datetime function"),
+                                                                 errdetail("unrecognized datetime format"),
+                                                                 errhint("use datetime template argument for explicit format specification"))));

The hint is confusing. If I understand correctly, no-arg datetime function supports all formats,
so if parsing failed, it must be an invalid argument and providing format explicitly won't help.

Custom format string may define format not enumerated in fmt_str[].
For instance, imagine "dd.mm.yyyy". In some cases custom format
string can fix the error. So, ISTM hint is OK.

I'm setting this back to "Needs review" waiting for either you or
Peter Eisentraut provide additional review.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0001-datetime-in-JsonbValue-4.patchapplication/octet-stream; name=0001-datetime-in-JsonbValue-4.patchDownload
commit 49a947c89a4c20d71f465eb88ed374e3d1a99ebc
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Thu May 16 21:48:26 2019 +0300

    Allow datetime values in JsonbValue
    
    SQL/JSON standard allows manipulation with datetime values.  So, it appears to
    be convinient to allow datetime values to be represented in JsonbValue struct.
    These datetime values are allowed for temporary representation only.  During
    serialization datetime values are converted into strings.
    
    SQL/JSON requires writing timestamps with timezone in the same timezone offset
    as they were parsed.  This is why we allow storage of timezone offset in
    JsonbValue struct.  For the same reason timezone offset argument is added to
    JsonEncodeDateTime() function.

diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index 26d293709aa..7440f77cbe5 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -1506,7 +1506,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 			{
 				char		buf[MAXDATELEN + 1];
 
-				JsonEncodeDateTime(buf, val, DATEOID);
+				JsonEncodeDateTime(buf, val, DATEOID, NULL);
 				appendStringInfo(result, "\"%s\"", buf);
 			}
 			break;
@@ -1514,7 +1514,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 			{
 				char		buf[MAXDATELEN + 1];
 
-				JsonEncodeDateTime(buf, val, TIMESTAMPOID);
+				JsonEncodeDateTime(buf, val, TIMESTAMPOID, NULL);
 				appendStringInfo(result, "\"%s\"", buf);
 			}
 			break;
@@ -1522,7 +1522,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 			{
 				char		buf[MAXDATELEN + 1];
 
-				JsonEncodeDateTime(buf, val, TIMESTAMPTZOID);
+				JsonEncodeDateTime(buf, val, TIMESTAMPTZOID, NULL);
 				appendStringInfo(result, "\"%s\"", buf);
 			}
 			break;
@@ -1550,10 +1550,11 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 
 /*
  * Encode 'value' of datetime type 'typid' into JSON string in ISO format using
- * optionally preallocated buffer 'buf'.
+ * optionally preallocated buffer 'buf'.  Optional 'tzp' determines time-zone
+ * offset (in seconds) in which we want to show timestamptz.
  */
 char *
-JsonEncodeDateTime(char *buf, Datum value, Oid typid)
+JsonEncodeDateTime(char *buf, Datum value, Oid typid, const int *tzp)
 {
 	if (!buf)
 		buf = palloc(MAXDATELEN + 1);
@@ -1630,11 +1631,30 @@ JsonEncodeDateTime(char *buf, Datum value, Oid typid)
 				const char *tzn = NULL;
 
 				timestamp = DatumGetTimestampTz(value);
+
+				/*
+				 * If time-zone is specified, we apply a time-zone shift,
+				 * convert timestamptz to pg_tm as if it was without
+				 * time-zone, and then use specified time-zone for encoding
+				 * timestamp into a string.
+				 */
+				if (tzp)
+				{
+					tz = *tzp;
+					timestamp -= (TimestampTz) tz * USECS_PER_SEC;
+				}
+
 				/* Same as timestamptz_out(), but forcing DateStyle */
 				if (TIMESTAMP_NOT_FINITE(timestamp))
 					EncodeSpecialTimestamp(timestamp, buf);
-				else if (timestamp2tm(timestamp, &tz, &tm, &fsec, &tzn, NULL) == 0)
+				else if (timestamp2tm(timestamp, tzp ? NULL : &tz, &tm, &fsec,
+									  tzp ? NULL : &tzn, NULL) == 0)
+				{
+					if (tzp)
+						tm.tm_isdst = 1;	/* set time-zone presence flag */
+
 					EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf);
+				}
 				else
 					ereport(ERROR,
 							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 69f41ab4556..74b4bbe44c6 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -206,6 +206,24 @@ JsonbTypeName(JsonbValue *jbv)
 			return "boolean";
 		case jbvNull:
 			return "null";
+		case jbvDatetime:
+			switch (jbv->val.datetime.typid)
+			{
+				case DATEOID:
+					return "date";
+				case TIMEOID:
+					return "time without time zone";
+				case TIMETZOID:
+					return "time with time zone";
+				case TIMESTAMPOID:
+					return "timestamp without time zone";
+				case TIMESTAMPTZOID:
+					return "timestamp with time zone";
+				default:
+					elog(ERROR, "unrecognized jsonb value datetime type: %d",
+						 jbv->val.datetime.typid);
+			}
+			return "unknown";
 		default:
 			elog(ERROR, "unrecognized jsonb value type: %d", jbv->type);
 			return "unknown";
@@ -805,17 +823,20 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result,
 				break;
 			case JSONBTYPE_DATE:
 				jb.type = jbvString;
-				jb.val.string.val = JsonEncodeDateTime(NULL, val, DATEOID);
+				jb.val.string.val = JsonEncodeDateTime(NULL, val,
+													   DATEOID, NULL);
 				jb.val.string.len = strlen(jb.val.string.val);
 				break;
 			case JSONBTYPE_TIMESTAMP:
 				jb.type = jbvString;
-				jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPOID);
+				jb.val.string.val = JsonEncodeDateTime(NULL, val,
+													   TIMESTAMPOID, NULL);
 				jb.val.string.len = strlen(jb.val.string.val);
 				break;
 			case JSONBTYPE_TIMESTAMPTZ:
 				jb.type = jbvString;
-				jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPTZOID);
+				jb.val.string.val = JsonEncodeDateTime(NULL, val,
+													   TIMESTAMPTZOID, NULL);
 				jb.val.string.len = strlen(jb.val.string.val);
 				break;
 			case JSONBTYPE_JSONCAST:
diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index 7969f6f5843..c5496733a10 100644
--- a/src/backend/utils/adt/jsonb_util.c
+++ b/src/backend/utils/adt/jsonb_util.c
@@ -14,9 +14,12 @@
 #include "postgres.h"
 
 #include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
 #include "miscadmin.h"
 #include "utils/builtins.h"
+#include "utils/datetime.h"
 #include "utils/hashutils.h"
+#include "utils/jsonapi.h"
 #include "utils/jsonb.h"
 #include "utils/memutils.h"
 #include "utils/varlena.h"
@@ -242,6 +245,8 @@ compareJsonbContainers(JsonbContainer *a, JsonbContainer *b)
 						break;
 					case jbvBinary:
 						elog(ERROR, "unexpected jbvBinary value");
+					case jbvDatetime:
+						elog(ERROR, "unexpected jbvDatetime value");
 				}
 			}
 			else
@@ -1749,6 +1754,22 @@ convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal)
 				JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE;
 			break;
 
+		case jbvDatetime:
+			{
+				char		buf[MAXDATELEN + 1];
+				size_t		len;
+
+				JsonEncodeDateTime(buf,
+								   scalarVal->val.datetime.value,
+								   scalarVal->val.datetime.typid,
+								   &scalarVal->val.datetime.tz);
+				len = strlen(buf);
+				appendToBuffer(buffer, buf, len);
+
+				*jentry = len;
+			}
+			break;
+
 		default:
 			elog(ERROR, "invalid jsonb scalar type");
 	}
diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h
index 5f4d479a7b2..b707b09ce31 100644
--- a/src/include/utils/jsonapi.h
+++ b/src/include/utils/jsonapi.h
@@ -161,6 +161,7 @@ extern Jsonb *transform_jsonb_string_values(Jsonb *jsonb, void *action_state,
 extern text *transform_json_string_values(text *json, void *action_state,
 										  JsonTransformStringValuesAction transform_action);
 
-extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid);
+extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid,
+								const int *tzp);
 
 #endif							/* JSONAPI_H */
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index 2fe7d32fec2..ec66d150263 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -241,7 +241,15 @@ enum jbvType
 	jbvArray = 0x10,
 	jbvObject,
 	/* Binary (i.e. struct Jsonb) jbvArray/jbvObject */
-	jbvBinary
+	jbvBinary,
+
+	/*
+	 * Virtual types.
+	 *
+	 * These types are used only for in-memory JSON processing and serialized
+	 * into JSON strings when outputted to json/jsonb.
+	 */
+	jbvDatetime = 0x20,
 };
 
 /*
@@ -282,11 +290,21 @@ struct JsonbValue
 			int			len;
 			JsonbContainer *data;
 		}			binary;		/* Array or object, in on-disk format */
+
+		struct
+		{
+			Datum		value;
+			Oid			typid;
+			int32		typmod;
+			int			tz;		/* Numeric time zone, in seconds for
+								 * TimestampTz data type */
+		}			datetime;
 	}			val;
 };
 
-#define IsAJsonbScalar(jsonbval)	((jsonbval)->type >= jbvNull && \
-									 (jsonbval)->type <= jbvBool)
+#define IsAJsonbScalar(jsonbval)	(((jsonbval)->type >= jbvNull && \
+									  (jsonbval)->type <= jbvBool) || \
+									  (jsonbval)->type == jbvDatetime)
 
 /*
  * Key/value pair within an Object.
0002-datetime-conversion-for-jsonpath-4.patchapplication/octet-stream; name=0002-datetime-conversion-for-jsonpath-4.patchDownload
commit 639b86bf40e55028d5e2b2dec4fcff536777bf02
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Wed Jan 23 05:55:49 2019 +0300

    Improve datetime conversion for upcoming jsonpath .datetime() method
    
    Jsonpath language (part of SQL/JSON standard) includes functions for datetime
    conversion.  In order to support that, we have to extend our infrastructure
    in following ways.
    
      1. FF1-FF6 format patterns implementing different fractions of second.  FF3
         and FF6 are effectively just synonyms for MS and US.  But other fractions
         were not implemented yet.
      2. to_datetime() internal function, which dynamically determines result
         datatype depending on format string.
      3. Strict parsing mode, which doesn't allow trailing spaces and unmatched
         format patterns.
    
    The first improvement is already user-visible and can use used in datetime
    parsing/printing functions.  Other improvements are internal, they will be
    user-visible together with jsonpath.
    
    Code was written by Nikita Glukhov and Teodor Sigaev, revised by me.
    Documentation was written by Oleg Bartunov and Liudmila Mantrova.  The work
    was inspired by Oleg Bartunov.
    
    Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
    Author: Nikita Glukhov, Teodor Sigaev, Alexander Korotkov, Oleg Bartunov, Liudmila Mantrova
    Reviewed-by: Andrew Dunstan, Pavel Stehule, Tomas Vondra, Alexander Korotkov

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index c2f5a75ff67..afde81664bb 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -6122,6 +6122,30 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
         <entry><literal>US</literal></entry>
         <entry>microsecond (000000-999999)</entry>
        </row>
+       <row>
+        <entry><literal>FF1</literal></entry>
+        <entry>decisecond (0-9)</entry>
+       </row>
+       <row>
+        <entry><literal>FF2</literal></entry>
+        <entry>centisecond (00-99)</entry>
+       </row>
+       <row>
+        <entry><literal>FF3</literal></entry>
+        <entry>millisecond (000-999)</entry>
+       </row>
+       <row>
+        <entry><literal>FF4</literal></entry>
+        <entry>tenth of a millisecond (0000-9999)</entry>
+       </row>
+       <row>
+        <entry><literal>FF5</literal></entry>
+        <entry>hundredth of a millisecond (00000-99999)</entry>
+       </row>
+       <row>
+        <entry><literal>FF6</literal></entry>
+        <entry>microsecond (000000-999999)</entry>
+       </row>
        <row>
         <entry><literal>SSSS</literal></entry>
         <entry>seconds past midnight (0-86399)</entry>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 4b1afb10f92..9e291b5c7bc 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -41,11 +41,6 @@
 #endif
 
 
-static int	tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
-static int	tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
-static void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
-
-
 /* common code for timetypmodin and timetztypmodin */
 static int32
 anytime_typmodin(bool istz, ArrayType *ta)
@@ -1203,7 +1198,7 @@ time_in(PG_FUNCTION_ARGS)
 /* tm2time()
  * Convert a tm structure to a time data type.
  */
-static int
+int
 tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result)
 {
 	*result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec)
@@ -1379,7 +1374,7 @@ time_scale(PG_FUNCTION_ARGS)
  * have a fundamental tie together but rather a coincidence of
  * implementation. - thomas
  */
-static void
+void
 AdjustTimeForTypmod(TimeADT *time, int32 typmod)
 {
 	static const int64 TimeScales[MAX_TIME_PRECISION + 1] = {
@@ -1957,7 +1952,7 @@ time_part(PG_FUNCTION_ARGS)
 /* tm2timetz()
  * Convert a tm structure to a time data type.
  */
-static int
+int
 tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result)
 {
 	result->time = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) *
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 206576d4bd3..68c2ae282e6 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -86,6 +86,7 @@
 #endif
 
 #include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
 #include "mb/pg_wchar.h"
 #include "utils/builtins.h"
 #include "utils/date.h"
@@ -436,7 +437,8 @@ typedef struct
 				clock,			/* 12 or 24 hour clock? */
 				tzsign,			/* +1, -1 or 0 if timezone info is absent */
 				tzh,
-				tzm;
+				tzm,
+				ff;				/* fractional precision */
 } TmFromChar;
 
 #define ZERO_tmfc(_X) memset(_X, 0, sizeof(TmFromChar))
@@ -596,6 +598,12 @@ typedef enum
 	DCH_Day,
 	DCH_Dy,
 	DCH_D,
+	DCH_FF1,
+	DCH_FF2,
+	DCH_FF3,
+	DCH_FF4,
+	DCH_FF5,
+	DCH_FF6,
 	DCH_FX,						/* global suffix */
 	DCH_HH24,
 	DCH_HH12,
@@ -645,6 +653,12 @@ typedef enum
 	DCH_dd,
 	DCH_dy,
 	DCH_d,
+	DCH_ff1,
+	DCH_ff2,
+	DCH_ff3,
+	DCH_ff4,
+	DCH_ff5,
+	DCH_ff6,
 	DCH_fx,
 	DCH_hh24,
 	DCH_hh12,
@@ -745,7 +759,13 @@ static const KeyWord DCH_keywords[] = {
 	{"Day", 3, DCH_Day, false, FROM_CHAR_DATE_NONE},
 	{"Dy", 2, DCH_Dy, false, FROM_CHAR_DATE_NONE},
 	{"D", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
-	{"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},	/* F */
+	{"FF1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE},	/* F */
+	{"FF2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE},
+	{"FF3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE},
+	{"FF4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE},
+	{"FF5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE},
+	{"FF6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE},
+	{"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
 	{"HH24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE},	/* H */
 	{"HH12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
 	{"HH", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
@@ -794,7 +814,13 @@ static const KeyWord DCH_keywords[] = {
 	{"dd", 2, DCH_DD, true, FROM_CHAR_DATE_GREGORIAN},
 	{"dy", 2, DCH_dy, false, FROM_CHAR_DATE_NONE},
 	{"d", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
-	{"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},	/* f */
+	{"ff1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE},	/* f */
+	{"ff2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE},
+	{"ff3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE},
+	{"ff4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE},
+	{"ff5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE},
+	{"ff6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE},
+	{"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
 	{"hh24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE},	/* h */
 	{"hh12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
 	{"hh", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
@@ -895,10 +921,10 @@ static const int DCH_index[KeyWord_INDEX_SIZE] = {
 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 	-1, -1, -1, -1, -1, DCH_A_D, DCH_B_C, DCH_CC, DCH_DAY, -1,
-	DCH_FX, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
+	DCH_FF1, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
 	DCH_P_M, DCH_Q, DCH_RM, DCH_SSSS, DCH_TZH, DCH_US, -1, DCH_WW, -1, DCH_Y_YYY,
 	-1, -1, -1, -1, -1, -1, -1, DCH_a_d, DCH_b_c, DCH_cc,
-	DCH_day, -1, DCH_fx, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
+	DCH_day, -1, DCH_ff1, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
 	-1, -1, DCH_p_m, DCH_q, DCH_rm, DCH_ssss, DCH_tz, DCH_us, -1, DCH_ww,
 	-1, DCH_y_yyy, -1, -1, -1, -1
 
@@ -962,6 +988,10 @@ typedef struct NUMProc
 			   *L_currency_symbol;
 } NUMProc;
 
+/* Return flags for DCH_from_char() */
+#define DCH_DATED	0x01
+#define DCH_TIMED	0x02
+#define DCH_ZONED	0x04
 
 /* ----------
  * Functions
@@ -977,7 +1007,8 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 
 static void DCH_to_char(FormatNode *node, bool is_interval,
 						TmToChar *in, char *out, Oid collid);
-static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out);
+static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
+						  bool strict);
 
 #ifdef DEBUG_TO_FROM_CHAR
 static void dump_index(const KeyWord *k, const int *index);
@@ -994,8 +1025,8 @@ static int	from_char_parse_int_len(int *dest, char **src, const int len, FormatN
 static int	from_char_parse_int(int *dest, char **src, FormatNode *node);
 static int	seq_search(char *name, const char *const *array, int type, int max, int *len);
 static int	from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
-static void do_to_timestamp(text *date_txt, text *fmt,
-							struct pg_tm *tm, fsec_t *fsec);
+static void do_to_timestamp(text *date_txt, text *fmt, bool strict,
+							struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags);
 static char *fill_str(char *str, int c, int max);
 static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
 static char *int_to_roman(int number);
@@ -2517,18 +2548,32 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 					str_numth(s, s, S_TH_TYPE(n->suffix));
 				s += strlen(s);
 				break;
-			case DCH_MS:		/* millisecond */
-				sprintf(s, "%03d", (int) (in->fsec / INT64CONST(1000)));
-				if (S_THth(n->suffix))
-					str_numth(s, s, S_TH_TYPE(n->suffix));
+#define DCH_to_char_fsec(frac_fmt, frac_val) \
+				sprintf(s, frac_fmt, (int) (frac_val)); \
+				if (S_THth(n->suffix)) \
+					str_numth(s, s, S_TH_TYPE(n->suffix)); \
 				s += strlen(s);
+			case DCH_FF1:		/* decisecond */
+				DCH_to_char_fsec("%01d", in->fsec / 100000);
+				break;
+			case DCH_FF2:		/* centisecond */
+				DCH_to_char_fsec("%02d", in->fsec / 10000);
+				break;
+			case DCH_FF3:
+			case DCH_MS:		/* millisecond */
+				DCH_to_char_fsec("%03d", in->fsec / 1000);
+				break;
+			case DCH_FF4:
+				DCH_to_char_fsec("%04d", in->fsec / 100);
 				break;
+			case DCH_FF5:
+				DCH_to_char_fsec("%05d", in->fsec / 10);
+				break;
+			case DCH_FF6:
 			case DCH_US:		/* microsecond */
-				sprintf(s, "%06d", (int) in->fsec);
-				if (S_THth(n->suffix))
-					str_numth(s, s, S_TH_TYPE(n->suffix));
-				s += strlen(s);
+				DCH_to_char_fsec("%06d", in->fsec);
 				break;
+#undef DCH_to_char_fsec
 			case DCH_SSSS:
 				sprintf(s, "%d", tm->tm_hour * SECS_PER_HOUR +
 						tm->tm_min * SECS_PER_MINUTE +
@@ -3010,13 +3055,15 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 /* ----------
  * Process a string as denoted by a list of FormatNodes.
  * The TmFromChar struct pointed to by 'out' is populated with the results.
+ * 'strict' enables error reporting on unmatched trailing characters in input or
+ * format strings patterns.
  *
  * Note: we currently don't have any to_interval() function, so there
  * is no need here for INVALID_FOR_INTERVAL checks.
  * ----------
  */
 static void
-DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
+DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 {
 	FormatNode *n;
 	char	   *s;
@@ -3153,8 +3200,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 
 				SKIP_THth(s, n->suffix);
 				break;
+			case DCH_FF1:
+			case DCH_FF2:
+			case DCH_FF3:
+			case DCH_FF4:
+			case DCH_FF5:
+			case DCH_FF6:
+				out->ff = n->key->id - DCH_FF1 + 1;
+				/* fall through */
 			case DCH_US:		/* microsecond */
-				len = from_char_parse_int_len(&out->us, &s, 6, n);
+				len = from_char_parse_int_len(&out->us, &s,
+											  n->key->id == DCH_US ? 6 :
+											  out->ff, n);
 
 				out->us *= len == 1 ? 100000 :
 					len == 2 ? 10000 :
@@ -3380,6 +3437,23 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 			}
 		}
 	}
+
+	if (strict)
+	{
+		if (n->type != NODE_TYPE_END)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+					 errmsg("input string is too short for datetime format")));
+
+		while (*s != '\0' && isspace((unsigned char) *s))
+			s++;
+
+		if (*s != '\0')
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+					 errmsg("trailing characters remain in input string after "
+							"datetime format")));
+	}
 }
 
 /*
@@ -3400,6 +3474,109 @@ DCH_prevent_counter_overflow(void)
 	}
 }
 
+/* Get mask of date/time/zone components present in format nodes. */
+static int
+DCH_datetime_type(FormatNode *node)
+{
+	FormatNode *n;
+	int			flags = 0;
+
+	for (n = node; n->type != NODE_TYPE_END; n++)
+	{
+		if (n->type != NODE_TYPE_ACTION)
+			continue;
+
+		switch (n->key->id)
+		{
+			case DCH_FX:
+				break;
+			case DCH_A_M:
+			case DCH_P_M:
+			case DCH_a_m:
+			case DCH_p_m:
+			case DCH_AM:
+			case DCH_PM:
+			case DCH_am:
+			case DCH_pm:
+			case DCH_HH:
+			case DCH_HH12:
+			case DCH_HH24:
+			case DCH_MI:
+			case DCH_SS:
+			case DCH_MS:		/* millisecond */
+			case DCH_US:		/* microsecond */
+			case DCH_FF1:
+			case DCH_FF2:
+			case DCH_FF3:
+			case DCH_FF4:
+			case DCH_FF5:
+			case DCH_FF6:
+			case DCH_SSSS:
+				flags |= DCH_TIMED;
+				break;
+			case DCH_tz:
+			case DCH_TZ:
+			case DCH_OF:
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("formatting field \"%s\" is only supported in to_char",
+								n->key->name)));
+				flags |= DCH_ZONED;
+				break;
+			case DCH_TZH:
+			case DCH_TZM:
+				flags |= DCH_ZONED;
+				break;
+			case DCH_A_D:
+			case DCH_B_C:
+			case DCH_a_d:
+			case DCH_b_c:
+			case DCH_AD:
+			case DCH_BC:
+			case DCH_ad:
+			case DCH_bc:
+			case DCH_MONTH:
+			case DCH_Month:
+			case DCH_month:
+			case DCH_MON:
+			case DCH_Mon:
+			case DCH_mon:
+			case DCH_MM:
+			case DCH_DAY:
+			case DCH_Day:
+			case DCH_day:
+			case DCH_DY:
+			case DCH_Dy:
+			case DCH_dy:
+			case DCH_DDD:
+			case DCH_IDDD:
+			case DCH_DD:
+			case DCH_D:
+			case DCH_ID:
+			case DCH_WW:
+			case DCH_Q:
+			case DCH_CC:
+			case DCH_Y_YYY:
+			case DCH_YYYY:
+			case DCH_IYYY:
+			case DCH_YYY:
+			case DCH_IYY:
+			case DCH_YY:
+			case DCH_IY:
+			case DCH_Y:
+			case DCH_I:
+			case DCH_RM:
+			case DCH_rm:
+			case DCH_W:
+			case DCH_J:
+				flags |= DCH_DATED;
+				break;
+		}
+	}
+
+	return flags;
+}
+
 /* select a DCHCacheEntry to hold the given format picture */
 static DCHCacheEntry *
 DCH_cache_getnew(const char *str)
@@ -3688,8 +3865,9 @@ to_timestamp(PG_FUNCTION_ARGS)
 	int			tz;
 	struct pg_tm tm;
 	fsec_t		fsec;
+	int			fprec;
 
-	do_to_timestamp(date_txt, fmt, &tm, &fsec);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL);
 
 	/* Use the specified time zone, if any. */
 	if (tm.tm_zone)
@@ -3707,6 +3885,10 @@ to_timestamp(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 				 errmsg("timestamp out of range")));
 
+	/* Use the specified fractional precision, if any. */
+	if (fprec)
+		AdjustTimestampForTypmod(&result, fprec);
+
 	PG_RETURN_TIMESTAMP(result);
 }
 
@@ -3724,7 +3906,7 @@ to_date(PG_FUNCTION_ARGS)
 	struct pg_tm tm;
 	fsec_t		fsec;
 
-	do_to_timestamp(date_txt, fmt, &tm, &fsec);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL);
 
 	/* Prevent overflow in Julian-day routines */
 	if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3745,11 +3927,169 @@ to_date(PG_FUNCTION_ARGS)
 	PG_RETURN_DATEADT(result);
 }
 
+/*
+ * Make datetime type from 'date_txt' which is formated at argument 'fmt'.
+ * Actual datatype (returned in 'typid', 'typmod') is determined by
+ * presence of date/time/zone components in the format string.
+ *
+ * When timezone component is present, corresponding offset is set to '*tz'.
+ */
+Datum
+parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
+			   int32 *typmod, int *tz)
+{
+	struct pg_tm tm;
+	fsec_t		fsec;
+	int			fprec = 0;
+	int			flags;
+
+	do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags);
+
+	*typmod = fprec ? fprec : -1;	/* fractional part precision */
+
+	if (flags & DCH_DATED)
+	{
+		if (flags & DCH_TIMED)
+		{
+			if (flags & DCH_ZONED)
+			{
+				TimestampTz result;
+
+				if (tm.tm_zone)
+				{
+					int			dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
+
+					if (dterr)
+						DateTimeParseError(dterr, text_to_cstring(date_txt), "timestamptz");
+				}
+				else
+				{
+					if (*tz == PG_INT32_MIN)
+						ereport(ERROR,
+								(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								 errmsg("missing time-zone in input string for type timestamptz")));
+				}
+
+				if (tm2timestamp(&tm, fsec, tz, &result) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("timestamptz out of range")));
+
+				AdjustTimestampForTypmod(&result, *typmod);
+
+				*typid = TIMESTAMPTZOID;
+				return TimestampTzGetDatum(result);
+			}
+			else
+			{
+				Timestamp	result;
+
+				if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("timestamp out of range")));
+
+				AdjustTimestampForTypmod(&result, *typmod);
+
+				*typid = TIMESTAMPOID;
+				return TimestampGetDatum(result);
+			}
+		}
+		else
+		{
+			if (flags & DCH_ZONED)
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+						 errmsg("datetime format is zoned but not timed")));
+			}
+			else
+			{
+				DateADT		result;
+
+				/* Prevent overflow in Julian-day routines */
+				if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("date out of range: \"%s\"",
+									text_to_cstring(date_txt))));
+
+				result = 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(result))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("date out of range: \"%s\"",
+									text_to_cstring(date_txt))));
+
+				*typid = DATEOID;
+				return DateADTGetDatum(result);
+			}
+		}
+	}
+	else if (flags & DCH_TIMED)
+	{
+		if (flags & DCH_ZONED)
+		{
+			TimeTzADT  *result = palloc(sizeof(TimeTzADT));
+
+			if (tm.tm_zone)
+			{
+				int			dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
+
+				if (dterr)
+					DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz");
+			}
+			else
+			{
+				if (*tz == PG_INT32_MIN)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							 errmsg("missing time-zone in input string for type timetz")));
+			}
+
+			if (tm2timetz(&tm, fsec, *tz, result) != 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("timetz out of range")));
+
+			AdjustTimeForTypmod(&result->time, *typmod);
+
+			*typid = TIMETZOID;
+			return TimeTzADTPGetDatum(result);
+		}
+		else
+		{
+			TimeADT		result;
+
+			if (tm2time(&tm, fsec, &result) != 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("time out of range")));
+
+			AdjustTimeForTypmod(&result, *typmod);
+
+			*typid = TIMEOID;
+			return TimeADTGetDatum(result);
+		}
+	}
+	else
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+				 errmsg("datetime format is not dated and not timed")));
+	}
+
+	return (Datum) 0;
+}
+
 /*
  * do_to_timestamp: shared code for to_timestamp and to_date
  *
  * Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm
- * and fractional seconds.
+ * and fractional seconds and fractional precision.
  *
  * We parse 'fmt' into a list of FormatNodes, which is then passed to
  * DCH_from_char to populate a TmFromChar with the parsed contents of
@@ -3757,10 +4097,15 @@ to_date(PG_FUNCTION_ARGS)
  *
  * The TmFromChar is then analysed and converted into the final results in
  * struct 'tm' and 'fsec'.
+ *
+ * Bit mask of date/time/zone components found in 'fmt' is returned in 'flags'.
+ *
+ * 'strict' enables error reporting on unmatched trailing characters in input or
+ * format strings patterns.
  */
 static void
-do_to_timestamp(text *date_txt, text *fmt,
-				struct pg_tm *tm, fsec_t *fsec)
+do_to_timestamp(text *date_txt, text *fmt, bool strict,
+				struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags)
 {
 	FormatNode *format;
 	TmFromChar	tmfc;
@@ -3813,9 +4158,13 @@ do_to_timestamp(text *date_txt, text *fmt,
 		/* dump_index(DCH_keywords, DCH_index); */
 #endif
 
-		DCH_from_char(format, date_str, &tmfc);
+		DCH_from_char(format, date_str, &tmfc, strict);
 
 		pfree(fmt_str);
+
+		if (flags)
+			*flags = DCH_datetime_type(format);
+
 		if (!incache)
 			pfree(format);
 	}
@@ -3997,6 +4346,8 @@ do_to_timestamp(text *date_txt, text *fmt,
 		*fsec += tmfc.ms * 1000;
 	if (tmfc.us)
 		*fsec += tmfc.us;
+	if (fprec)
+		*fprec = tmfc.ff;		/* fractional precision, if specified */
 
 	/* Range-check date fields according to bit mask computed above */
 	if (fmask != 0)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 5861ffbbc97..2931bd58c0d 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -70,7 +70,6 @@ typedef struct
 
 static TimeOffset time2t(const int hour, const int min, const int sec, const fsec_t fsec);
 static Timestamp dt2local(Timestamp dt, int timezone);
-static void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
 static void AdjustIntervalForTypmod(Interval *interval, int32 typmod);
 static TimestampTz timestamp2timestamptz(Timestamp timestamp);
 static Timestamp timestamptz2timestamp(TimestampTz timestamp);
@@ -333,7 +332,7 @@ timestamp_scale(PG_FUNCTION_ARGS)
  * AdjustTimestampForTypmod --- round off a timestamp to suit given typmod
  * Works for either timestamp or timestamptz.
  */
-static void
+void
 AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
 {
 	static const int64 TimestampScales[MAX_TIMESTAMP_PRECISION + 1] = {
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index bec129aff1c..bd15bfa5bb0 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -76,5 +76,8 @@ extern TimeTzADT *GetSQLCurrentTime(int32 typmod);
 extern TimeADT GetSQLLocalTime(int32 typmod);
 extern int	time2tm(TimeADT time, struct pg_tm *tm, fsec_t *fsec);
 extern int	timetz2tm(TimeTzADT *time, struct pg_tm *tm, fsec_t *fsec, int *tzp);
+extern int	tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
+extern int	tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
+extern void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
 
 #endif							/* DATE_H */
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index b8a199cdded..0cafdd26538 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -336,4 +336,6 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
 												   int n);
 extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
 
+extern void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
+
 #endif							/* DATETIME_H */
diff --git a/src/include/utils/formatting.h b/src/include/utils/formatting.h
index 5b275dc9850..a390302e89c 100644
--- a/src/include/utils/formatting.h
+++ b/src/include/utils/formatting.h
@@ -28,4 +28,7 @@ extern char *asc_tolower(const char *buff, size_t nbytes);
 extern char *asc_toupper(const char *buff, size_t nbytes);
 extern char *asc_initcap(const char *buff, size_t nbytes);
 
+extern Datum parse_datetime(text *date_txt, text *fmt, bool strict,
+							Oid *typid, int32 *typmod, int *tz);
+
 #endif
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index b2b45773339..74ecb7c10e6 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -2786,6 +2786,85 @@ SELECT to_timestamp('2011-12-18 11:38 20',     'YYYY-MM-DD HH12:MI TZM');
  Sun Dec 18 03:18:00 2011 PST
 (1 row)
 
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |         to_timestamp         
+---+------------------------------
+ 1 | Fri Nov 02 12:34:56 2018 PDT
+ 2 | Fri Nov 02 12:34:56 2018 PDT
+ 3 | Fri Nov 02 12:34:56 2018 PDT
+ 4 | Fri Nov 02 12:34:56 2018 PDT
+ 5 | Fri Nov 02 12:34:56 2018 PDT
+ 6 | Fri Nov 02 12:34:56 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |          to_timestamp          
+---+--------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.1 2018 PDT
+ 3 | Fri Nov 02 12:34:56.1 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |          to_timestamp           
+---+---------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.12 2018 PDT
+ 4 | Fri Nov 02 12:34:56.12 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |           to_timestamp           
+---+----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.123 2018 PDT
+ 5 | Fri Nov 02 12:34:56.123 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |           to_timestamp            
+---+-----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1234 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |            to_timestamp            
+---+------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12345 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12345 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |            to_timestamp             
+---+-------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12346 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123456 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ERROR:  date/time field value out of range: "2018-11-02 12:34:56.123456789"
 --
 -- Check handling of multiple spaces in format and/or input
 --
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index 715680e3302..f772b07d5a4 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -1584,6 +1584,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
             | 2001 1 1 1 1 1 1
 (65 rows)
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamp),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+ to_char_12 |                              to_char                               
+------------+--------------------------------------------------------------------
+            | 0 00 000 0000 00000 000000  0 00 000 0000 00000 000000  000 000000
+            | 7 78 780 7800 78000 780000  7 78 780 7800 78000 780000  780 780000
+            | 7 78 789 7890 78901 789010  7 78 789 7890 78901 789010  789 789010
+            | 7 78 789 7890 78901 789012  7 78 789 7890 78901 789012  789 789012
+(4 rows)
+
 -- timestamp numeric fields constructor
 SELECT make_timestamp(2014,12,28,6,30,45.887);
         make_timestamp        
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 5551fa6610e..2d6a71ca64b 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -1704,6 +1704,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
             | 2001 1 1 1 1 1 1
 (66 rows)
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamptz),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+ to_char_12 |                              to_char                               
+------------+--------------------------------------------------------------------
+            | 0 00 000 0000 00000 000000  0 00 000 0000 00000 000000  000 000000
+            | 7 78 780 7800 78000 780000  7 78 780 7800 78000 780000  780 780000
+            | 7 78 789 7890 78901 789010  7 78 789 7890 78901 789010  789 789010
+            | 7 78 789 7890 78901 789012  7 78 789 7890 78901 789012  789 789012
+(4 rows)
+
 -- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
 SET timezone = '00:00';
 SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index e356dd563ee..3c8580397ac 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -402,6 +402,15 @@ SELECT to_timestamp('2011-12-18 11:38 +05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
 SELECT to_timestamp('2011-12-18 11:38 -05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
 SELECT to_timestamp('2011-12-18 11:38 20',     'YYYY-MM-DD HH12:MI TZM');
 
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+
 --
 -- Check handling of multiple spaces in format and/or input
 --
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index 031b22bc3c1..329987f7eaa 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -224,5 +224,13 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
 SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
    FROM TIMESTAMP_TBL;
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamp),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+
 -- timestamp numeric fields constructor
 SELECT make_timestamp(2014,12,28,6,30,45.887);
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index 28c76d6b72c..f5fee639a01 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -248,6 +248,14 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
 SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
    FROM TIMESTAMPTZ_TBL;
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamptz),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+
 -- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
 SET timezone = '00:00';
 SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
0003-error-suppression-for-datetime-4.patchapplication/octet-stream; name=0003-error-suppression-for-datetime-4.patchDownload
commit 06229f7881599ee86de3e87d9c57f92f32f6ef1d
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Mon May 27 10:29:07 2019 +0300

    Error suppression support for upcoming jsonpath .datetime() method
    
    Add support of error suppression in some date and time manipulation functions
    as it's required for jsonpath .datetime() method support.  This commit doesn't
    use PG_TRY()/PG_CATCH() in order to implement that.  Instead, it provides
    internal versions of date and time functions used, which support error
    suppression.

diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 9e291b5c7bc..1456414e399 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -550,13 +550,15 @@ date_mii(PG_FUNCTION_ARGS)
 	PG_RETURN_DATEADT(result);
 }
 
+
 /*
- * Internal routines for promoting date to timestamp and timestamp with
- * time zone
+ * Promote date to timestamp.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero returned.
  */
-
-static Timestamp
-date2timestamp(DateADT dateVal)
+Timestamp
+date2timestamp_opt_error(DateADT dateVal, bool *have_error)
 {
 	Timestamp	result;
 
@@ -572,9 +574,19 @@ date2timestamp(DateADT dateVal)
 		 * boundary need be checked for overflow.
 		 */
 		if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("date out of range for timestamp")));
+		{
+			if (have_error)
+			{
+				*have_error = true;
+				return (Timestamp) 0;
+			}
+			else
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("date out of range for timestamp")));
+			}
+		}
 
 		/* date is days since 2000, timestamp is microseconds since same... */
 		result = dateVal * USECS_PER_DAY;
@@ -583,8 +595,23 @@ date2timestamp(DateADT dateVal)
 	return result;
 }
 
+/*
+ * Single-argument version of date2timestamp_opt_error().
+ */
 static TimestampTz
-date2timestamptz(DateADT dateVal)
+date2timestamp(DateADT dateVal)
+{
+	return date2timestamp_opt_error(dateVal, NULL);
+}
+
+/*
+ * Promote date to timestamp with time zone.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero returned.
+ */
+TimestampTz
+date2timestamptz_opt_error(DateADT dateVal, bool *have_error)
 {
 	TimestampTz result;
 	struct pg_tm tt,
@@ -603,9 +630,19 @@ date2timestamptz(DateADT dateVal)
 		 * boundary need be checked for overflow.
 		 */
 		if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("date out of range for timestamp")));
+		{
+			if (have_error)
+			{
+				*have_error = true;
+				return (TimestampTz) 0;
+			}
+			else
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("date out of range for timestamp")));
+			}
+		}
 
 		j2date(dateVal + POSTGRES_EPOCH_JDATE,
 			   &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday));
@@ -621,14 +658,33 @@ date2timestamptz(DateADT dateVal)
 		 * of time zone, check for allowed timestamp range after adding tz.
 		 */
 		if (!IS_VALID_TIMESTAMP(result))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("date out of range for timestamp")));
+		{
+			if (have_error)
+			{
+				*have_error = true;
+				return (TimestampTz) 0;
+			}
+			else
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("date out of range for timestamp")));
+			}
+		}
 	}
 
 	return result;
 }
 
+/*
+ * Single-argument version of date2timestamptz_opt_error().
+ */
+static TimestampTz
+date2timestamptz(DateADT dateVal)
+{
+	return date2timestamptz_opt_error(dateVal, NULL);
+}
+
 /*
  * date2timestamp_no_overflow
  *
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 68c2ae282e6..498ec5a3a12 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -98,6 +98,43 @@
 #include "utils/numeric.h"
 #include "utils/pg_locale.h"
 
+/* ----------
+ * Convenience macros for error handling
+ * ----------
+ *
+ * Two macros below help to handle errors in functions, which take
+ * 'bool *have_error' argument.  When this argument is not NULL, it's expected
+ * that function will suppress ereports when possible.  Instead it should
+ * return some default value and set *have_error flag.
+ *
+ * RETURN_ERROR() macro intended to wrap ereport() calls.  When have_error
+ * function argument is not NULL, then instead of ereport'ing we set
+ * *have_error flag and go to on_error label.  It's supposed that jump
+ * resources will be freed and some 'default' value returned.
+ *
+ * CHECK_ERROR() jumps on_error label when *have_error flag is defined and set.
+ * It's supposed to be used for immediate exit from the function on error
+ * after call of another function with 'bool *have_error' argument.
+ */
+#define RETURN_ERROR(throw_error) \
+do { \
+	if (have_error) \
+	{ \
+		*have_error = true; \
+		goto on_error; \
+	} \
+	else \
+	{ \
+		throw_error; \
+	} \
+} while (0)
+
+#define CHECK_ERROR \
+do { \
+	if (have_error && *have_error) \
+		goto on_error; \
+} while (0)
+
 /* ----------
  * Routines type
  * ----------
@@ -1008,7 +1045,7 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 static void DCH_to_char(FormatNode *node, bool is_interval,
 						TmToChar *in, char *out, Oid collid);
 static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
-						  bool strict);
+						  bool strict, bool *have_error);
 
 #ifdef DEBUG_TO_FROM_CHAR
 static void dump_index(const KeyWord *k, const int *index);
@@ -1019,14 +1056,21 @@ static const char *get_th(char *num, int type);
 static char *str_numth(char *dest, char *num, int type);
 static int	adjust_partial_year_to_2020(int year);
 static int	strspace_len(char *str);
-static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode);
-static void from_char_set_int(int *dest, const int value, const FormatNode *node);
-static int	from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node);
-static int	from_char_parse_int(int *dest, char **src, FormatNode *node);
+static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode,
+							   bool *have_error);
+static void from_char_set_int(int *dest, const int value, const FormatNode *node,
+							  bool *have_error);
+static int	from_char_parse_int_len(int *dest, char **src, const int len,
+									FormatNode *node, bool *have_error);
+static int	from_char_parse_int(int *dest, char **src, FormatNode *node,
+								bool *have_error);
 static int	seq_search(char *name, const char *const *array, int type, int max, int *len);
-static int	from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
+static int	from_char_seq_search(int *dest, char **src,
+								 const char *const *array, int type, int max,
+								 FormatNode *node, bool *have_error);
 static void do_to_timestamp(text *date_txt, text *fmt, bool strict,
-							struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags);
+							struct pg_tm *tm, fsec_t *fsec, int *fprec,
+							int *flags, bool *have_error);
 static char *fill_str(char *str, int c, int max);
 static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
 static char *int_to_roman(int number);
@@ -2202,21 +2246,26 @@ strspace_len(char *str)
  *
  * Puke if the date mode has already been set, and the caller attempts to set
  * it to a conflicting mode.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
-from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode)
+from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode, bool *have_error)
 {
 	if (mode != FROM_CHAR_DATE_NONE)
 	{
 		if (tmfc->mode == FROM_CHAR_DATE_NONE)
 			tmfc->mode = mode;
 		else if (tmfc->mode != mode)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("invalid combination of date conventions"),
-					 errhint("Do not mix Gregorian and ISO week date "
-							 "conventions in a formatting template.")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("invalid combination of date conventions"),
+								  errhint("Do not mix Gregorian and ISO week date "
+										  "conventions in a formatting template."))));
 	}
+
+on_error:
+	return;
 }
 
 /*
@@ -2224,18 +2273,25 @@ from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode)
  *
  * Puke if the destination integer has previously been set to some other
  * non-zero value.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
-from_char_set_int(int *dest, const int value, const FormatNode *node)
+from_char_set_int(int *dest, const int value, const FormatNode *node,
+				  bool *have_error)
 {
 	if (*dest != 0 && *dest != value)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("conflicting values for \"%s\" field in formatting string",
-						node->key->name),
-				 errdetail("This value contradicts a previous setting for "
-						   "the same field type.")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("conflicting values for \"%s\" field in "
+									 "formatting string",
+									 node->key->name),
+							  errdetail("This value contradicts a previous setting "
+										"for the same field type."))));
 	*dest = value;
+
+on_error:
+	return;
 }
 
 /*
@@ -2257,9 +2313,13 @@ from_char_set_int(int *dest, const int value, const FormatNode *node)
  * Note that from_char_parse_int() provides a more convenient wrapper where
  * the length of the field is the same as the length of the format keyword (as
  * with DD and MI).
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and -1 is returned.
  */
 static int
-from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
+from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node,
+						bool *have_error)
 {
 	long		result;
 	char		copy[DCH_MAX_ITEM_SIZ + 1];
@@ -2292,51 +2352,60 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
 		char	   *last;
 
 		if (used < len)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("source string too short for \"%s\" formatting field",
-							node->key->name),
-					 errdetail("Field requires %d characters, but only %d "
-							   "remain.",
-							   len, used),
-					 errhint("If your source string is not fixed-width, try "
-							 "using the \"FM\" modifier.")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("source string too short for \"%s\" "
+										 "formatting field",
+										 node->key->name),
+								  errdetail("Field requires %d characters, "
+											"but only %d remain.",
+											len, used),
+								  errhint("If your source string is not fixed-width, "
+										  "try using the \"FM\" modifier."))));
 
 		errno = 0;
 		result = strtol(copy, &last, 10);
 		used = last - copy;
 
 		if (used > 0 && used < len)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("invalid value \"%s\" for \"%s\"",
-							copy, node->key->name),
-					 errdetail("Field requires %d characters, but only %d "
-							   "could be parsed.", len, used),
-					 errhint("If your source string is not fixed-width, try "
-							 "using the \"FM\" modifier.")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("invalid value \"%s\" for \"%s\"",
+										 copy, node->key->name),
+								  errdetail("Field requires %d characters, "
+											"but only %d could be parsed.",
+											len, used),
+								  errhint("If your source string is not fixed-width, "
+										  "try using the \"FM\" modifier."))));
 
 		*src += used;
 	}
 
 	if (*src == init)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("invalid value \"%s\" for \"%s\"",
-						copy, node->key->name),
-				 errdetail("Value must be an integer.")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("invalid value \"%s\" for \"%s\"",
+									 copy, node->key->name),
+							  errdetail("Value must be an integer."))));
 
 	if (errno == ERANGE || result < INT_MIN || result > INT_MAX)
-		ereport(ERROR,
-				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-				 errmsg("value for \"%s\" in source string is out of range",
-						node->key->name),
-				 errdetail("Value must be in the range %d to %d.",
-						   INT_MIN, INT_MAX)));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							  errmsg("value for \"%s\" in source string is out of range",
+									 node->key->name),
+							  errdetail("Value must be in the range %d to %d.",
+										INT_MIN, INT_MAX))));
 
 	if (dest != NULL)
-		from_char_set_int(dest, (int) result, node);
+	{
+		from_char_set_int(dest, (int) result, node, have_error);
+		CHECK_ERROR;
+	}
+
 	return *src - init;
+
+on_error:
+	return -1;
 }
 
 /*
@@ -2349,9 +2418,9 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
  * required length explicitly.
  */
 static int
-from_char_parse_int(int *dest, char **src, FormatNode *node)
+from_char_parse_int(int *dest, char **src, FormatNode *node, bool *have_error)
 {
-	return from_char_parse_int_len(dest, src, node->key->len, node);
+	return from_char_parse_int_len(dest, src, node->key->len, node, have_error);
 }
 
 /* ----------
@@ -2434,11 +2503,12 @@ seq_search(char *name, const char *const *array, int type, int max, int *len)
  * pointed to by 'dest', advance 'src' to the end of the part of the string
  * which matched, and return the number of characters consumed.
  *
- * If the string doesn't match, throw an error.
+ * If the string doesn't match, throw an error if 'have_error' is NULL,
+ * otherwise set '*have_error' and return -1.
  */
 static int
-from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max,
-					 FormatNode *node)
+from_char_seq_search(int *dest, char **src, const char *const *array, int type,
+					 int max, FormatNode *node, bool *have_error)
 {
 	int			len;
 
@@ -2450,15 +2520,18 @@ from_char_seq_search(int *dest, char **src, const char *const *array, int type,
 		Assert(max <= DCH_MAX_ITEM_SIZ);
 		strlcpy(copy, *src, max + 1);
 
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("invalid value \"%s\" for \"%s\"",
-						copy, node->key->name),
-				 errdetail("The given value did not match any of the allowed "
-						   "values for this field.")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("invalid value \"%s\" for \"%s\"",
+									 copy, node->key->name),
+							  errdetail("The given value did not match any of "
+										"the allowed values for this field."))));
 	}
 	*src += len;
 	return len;
+
+on_error:
+	return -1;
 }
 
 /* ----------
@@ -3060,10 +3133,13 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
  *
  * Note: we currently don't have any to_interval() function, so there
  * is no need here for INVALID_FOR_INTERVAL checks.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  * ----------
  */
 static void
-DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
+DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict,
+			  bool *have_error)
 {
 	FormatNode *n;
 	char	   *s;
@@ -3146,7 +3222,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 			continue;
 		}
 
-		from_char_set_mode(out, n->key->date_mode);
+		from_char_set_mode(out, n->key->date_mode, have_error);
+		CHECK_ERROR;
 
 		switch (n->key->id)
 		{
@@ -3158,8 +3235,10 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 			case DCH_a_m:
 			case DCH_p_m:
 				from_char_seq_search(&value, &s, ampm_strings_long,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->pm, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->pm, value % 2, n, have_error);
+				CHECK_ERROR;
 				out->clock = CLOCK_12_HOUR;
 				break;
 			case DCH_AM:
@@ -3167,30 +3246,37 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 			case DCH_am:
 			case DCH_pm:
 				from_char_seq_search(&value, &s, ampm_strings,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->pm, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->pm, value % 2, n, have_error);
+				CHECK_ERROR;
 				out->clock = CLOCK_12_HOUR;
 				break;
 			case DCH_HH:
 			case DCH_HH12:
-				from_char_parse_int_len(&out->hh, &s, 2, n);
+				from_char_parse_int_len(&out->hh, &s, 2, n, have_error);
+				CHECK_ERROR;
 				out->clock = CLOCK_12_HOUR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_HH24:
-				from_char_parse_int_len(&out->hh, &s, 2, n);
+				from_char_parse_int_len(&out->hh, &s, 2, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_MI:
-				from_char_parse_int(&out->mi, &s, n);
+				from_char_parse_int(&out->mi, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_SS:
-				from_char_parse_int(&out->ss, &s, n);
+				from_char_parse_int(&out->ss, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_MS:		/* millisecond */
-				len = from_char_parse_int_len(&out->ms, &s, 3, n);
+				len = from_char_parse_int_len(&out->ms, &s, 3, n, have_error);
+				CHECK_ERROR;
 
 				/*
 				 * 25 is 0.25 and 250 is 0.25 too; 025 is 0.025 and not 0.25
@@ -3211,7 +3297,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 			case DCH_US:		/* microsecond */
 				len = from_char_parse_int_len(&out->us, &s,
 											  n->key->id == DCH_US ? 6 :
-											  out->ff, n);
+											  out->ff, n, have_error);
+				CHECK_ERROR;
 
 				out->us *= len == 1 ? 100000 :
 					len == 2 ? 10000 :
@@ -3222,16 +3309,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_SSSS:
-				from_char_parse_int(&out->ssss, &s, n);
+				from_char_parse_int(&out->ssss, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_tz:
 			case DCH_TZ:
 			case DCH_OF:
-				ereport(ERROR,
-						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						 errmsg("formatting field \"%s\" is only supported in to_char",
-								n->key->name)));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									  errmsg("formatting field \"%s\" is only supported in to_char",
+											 n->key->name))));
+				CHECK_ERROR;
 				break;
 			case DCH_TZH:
 
@@ -3255,82 +3344,102 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 						out->tzsign = +1;
 				}
 
-				from_char_parse_int_len(&out->tzh, &s, 2, n);
+				from_char_parse_int_len(&out->tzh, &s, 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_TZM:
 				/* assign positive timezone sign if TZH was not seen before */
 				if (!out->tzsign)
 					out->tzsign = +1;
-				from_char_parse_int_len(&out->tzm, &s, 2, n);
+				from_char_parse_int_len(&out->tzm, &s, 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_A_D:
 			case DCH_B_C:
 			case DCH_a_d:
 			case DCH_b_c:
 				from_char_seq_search(&value, &s, adbc_strings_long,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->bc, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->bc, value % 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_AD:
 			case DCH_BC:
 			case DCH_ad:
 			case DCH_bc:
 				from_char_seq_search(&value, &s, adbc_strings,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->bc, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->bc, value % 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_MONTH:
 			case DCH_Month:
 			case DCH_month:
 				from_char_seq_search(&value, &s, months_full, ONE_UPPER,
-									 MAX_MONTH_LEN, n);
-				from_char_set_int(&out->mm, value + 1, n);
+									 MAX_MONTH_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, value + 1, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_MON:
 			case DCH_Mon:
 			case DCH_mon:
 				from_char_seq_search(&value, &s, months, ONE_UPPER,
-									 MAX_MON_LEN, n);
-				from_char_set_int(&out->mm, value + 1, n);
+									 MAX_MON_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, value + 1, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_MM:
-				from_char_parse_int(&out->mm, &s, n);
+				from_char_parse_int(&out->mm, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_DAY:
 			case DCH_Day:
 			case DCH_day:
 				from_char_seq_search(&value, &s, days, ONE_UPPER,
-									 MAX_DAY_LEN, n);
-				from_char_set_int(&out->d, value, n);
+									 MAX_DAY_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->d, value, n, have_error);
+				CHECK_ERROR;
 				out->d++;
 				break;
 			case DCH_DY:
 			case DCH_Dy:
 			case DCH_dy:
 				from_char_seq_search(&value, &s, days, ONE_UPPER,
-									 MAX_DY_LEN, n);
-				from_char_set_int(&out->d, value, n);
+									 MAX_DY_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->d, value, n, have_error);
+				CHECK_ERROR;
 				out->d++;
 				break;
 			case DCH_DDD:
-				from_char_parse_int(&out->ddd, &s, n);
+				from_char_parse_int(&out->ddd, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_IDDD:
-				from_char_parse_int_len(&out->ddd, &s, 3, n);
+				from_char_parse_int_len(&out->ddd, &s, 3, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_DD:
-				from_char_parse_int(&out->dd, &s, n);
+				from_char_parse_int(&out->dd, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_D:
-				from_char_parse_int(&out->d, &s, n);
+				from_char_parse_int(&out->d, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_ID:
-				from_char_parse_int_len(&out->d, &s, 1, n);
+				from_char_parse_int_len(&out->d, &s, 1, n, have_error);
+				CHECK_ERROR;
 				/* Shift numbering to match Gregorian where Sunday = 1 */
 				if (++out->d > 7)
 					out->d = 1;
@@ -3338,7 +3447,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 				break;
 			case DCH_WW:
 			case DCH_IW:
-				from_char_parse_int(&out->ww, &s, n);
+				from_char_parse_int(&out->ww, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Q:
@@ -3353,11 +3463,13 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 				 * We still parse the source string for an integer, but it
 				 * isn't stored anywhere in 'out'.
 				 */
-				from_char_parse_int((int *) NULL, &s, n);
+				from_char_parse_int((int *) NULL, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_CC:
-				from_char_parse_int(&out->cc, &s, n);
+				from_char_parse_int(&out->cc, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Y_YYY:
@@ -3369,11 +3481,12 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 
 					matched = sscanf(s, "%d,%03d%n", &millennia, &years, &nch);
 					if (matched < 2)
-						ereport(ERROR,
-								(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-								 errmsg("invalid input string for \"Y,YYY\"")));
+						RETURN_ERROR(ereport(ERROR,
+											 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+											  errmsg("invalid input string for \"Y,YYY\""))));
 					years += (millennia * 1000);
-					from_char_set_int(&out->year, years, n);
+					from_char_set_int(&out->year, years, n, have_error);
+					CHECK_ERROR;
 					out->yysz = 4;
 					s += nch;
 					SKIP_THth(s, n->suffix);
@@ -3381,47 +3494,62 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 				break;
 			case DCH_YYYY:
 			case DCH_IYYY:
-				from_char_parse_int(&out->year, &s, n);
+				from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
 				out->yysz = 4;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_YYY:
 			case DCH_IYY:
-				if (from_char_parse_int(&out->year, &s, n) < 4)
+				len = from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
+				if (len < 4)
 					out->year = adjust_partial_year_to_2020(out->year);
 				out->yysz = 3;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_YY:
 			case DCH_IY:
-				if (from_char_parse_int(&out->year, &s, n) < 4)
+				len = from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
+				if (len < 4)
 					out->year = adjust_partial_year_to_2020(out->year);
 				out->yysz = 2;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Y:
 			case DCH_I:
-				if (from_char_parse_int(&out->year, &s, n) < 4)
+				len = from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
+				if (len < 4)
 					out->year = adjust_partial_year_to_2020(out->year);
 				out->yysz = 1;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_RM:
 				from_char_seq_search(&value, &s, rm_months_upper,
-									 ALL_UPPER, MAX_RM_LEN, n);
-				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n);
+									 ALL_UPPER, MAX_RM_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value,
+								  n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_rm:
 				from_char_seq_search(&value, &s, rm_months_lower,
-									 ALL_LOWER, MAX_RM_LEN, n);
-				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n);
+									 ALL_LOWER, MAX_RM_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value,
+								  n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_W:
-				from_char_parse_int(&out->w, &s, n);
+				from_char_parse_int(&out->w, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_J:
-				from_char_parse_int(&out->j, &s, n);
+				from_char_parse_int(&out->j, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 		}
@@ -3441,19 +3569,22 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 	if (strict)
 	{
 		if (n->type != NODE_TYPE_END)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("input string is too short for datetime format")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("input string is too short for datetime format"))));
 
 		while (*s != '\0' && isspace((unsigned char) *s))
 			s++;
 
 		if (*s != '\0')
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("trailing characters remain in input string after "
-							"datetime format")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("trailing characters remain in input string "
+										 "after datetime format"))));
 	}
+
+on_error:
+	return;
 }
 
 /*
@@ -3474,9 +3605,13 @@ DCH_prevent_counter_overflow(void)
 	}
 }
 
-/* Get mask of date/time/zone components present in format nodes. */
+/*
+ * Get mask of date/time/zone components present in format nodes.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
+ */
 static int
-DCH_datetime_type(FormatNode *node)
+DCH_datetime_type(FormatNode *node, bool *have_error)
 {
 	FormatNode *n;
 	int			flags = 0;
@@ -3517,10 +3652,10 @@ DCH_datetime_type(FormatNode *node)
 			case DCH_tz:
 			case DCH_TZ:
 			case DCH_OF:
-				ereport(ERROR,
-						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						 errmsg("formatting field \"%s\" is only supported in to_char",
-								n->key->name)));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									  errmsg("formatting field \"%s\" is only supported in to_char",
+											 n->key->name))));
 				flags |= DCH_ZONED;
 				break;
 			case DCH_TZH:
@@ -3574,6 +3709,7 @@ DCH_datetime_type(FormatNode *node)
 		}
 	}
 
+on_error:
 	return flags;
 }
 
@@ -3867,7 +4003,7 @@ to_timestamp(PG_FUNCTION_ARGS)
 	fsec_t		fsec;
 	int			fprec;
 
-	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL, NULL);
 
 	/* Use the specified time zone, if any. */
 	if (tm.tm_zone)
@@ -3906,7 +4042,7 @@ to_date(PG_FUNCTION_ARGS)
 	struct pg_tm tm;
 	fsec_t		fsec;
 
-	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL, NULL);
 
 	/* Prevent overflow in Julian-day routines */
 	if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3933,17 +4069,21 @@ to_date(PG_FUNCTION_ARGS)
  * presence of date/time/zone components in the format string.
  *
  * When timezone component is present, corresponding offset is set to '*tz'.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero value is returned.
  */
 Datum
 parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
-			   int32 *typmod, int *tz)
+			   int32 *typmod, int *tz, bool *have_error)
 {
 	struct pg_tm tm;
 	fsec_t		fsec;
 	int			fprec = 0;
 	int			flags;
 
-	do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags);
+	do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags, have_error);
+	CHECK_ERROR;
 
 	*typmod = fprec ? fprec : -1;	/* fractional part precision */
 
@@ -3964,16 +4104,22 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 				}
 				else
 				{
-					if (*tz == PG_INT32_MIN)
-						ereport(ERROR,
-								(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-								 errmsg("missing time-zone in input string for type timestamptz")));
+					/*
+					 * Time zone is present in format string, but not in input
+					 * string.  Assuming do_to_timestamp() triggers no error
+					 * this should be possible only in non-strict case.
+					 */
+					Assert(!strict);
+
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+										  errmsg("missing time-zone in input string for type timestamptz"))));
 				}
 
 				if (tm2timestamp(&tm, fsec, tz, &result) != 0)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("timestamptz out of range")));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("timestamptz out of range"))));
 
 				AdjustTimestampForTypmod(&result, *typmod);
 
@@ -3985,9 +4131,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 				Timestamp	result;
 
 				if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("timestamp out of range")));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("timestamp out of range"))));
 
 				AdjustTimestampForTypmod(&result, *typmod);
 
@@ -3999,9 +4145,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 		{
 			if (flags & DCH_ZONED)
 			{
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-						 errmsg("datetime format is zoned but not timed")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+									  errmsg("datetime format is zoned but not timed"))));
 			}
 			else
 			{
@@ -4009,20 +4155,20 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 
 				/* Prevent overflow in Julian-day routines */
 				if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("date out of range: \"%s\"",
-									text_to_cstring(date_txt))));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("date out of range: \"%s\"",
+												 text_to_cstring(date_txt)))));
 
 				result = 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(result))
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("date out of range: \"%s\"",
-									text_to_cstring(date_txt))));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("date out of range: \"%s\"",
+												 text_to_cstring(date_txt)))));
 
 				*typid = DATEOID;
 				return DateADTGetDatum(result);
@@ -4040,20 +4186,26 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 				int			dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
 
 				if (dterr)
-					DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz");
+					RETURN_ERROR(DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz"));
 			}
 			else
 			{
-				if (*tz == PG_INT32_MIN)
-					ereport(ERROR,
-							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-							 errmsg("missing time-zone in input string for type timetz")));
+				/*
+				 * Time zone is present in format string, but not in input
+				 * string.  Assuming do_to_timestamp() triggers no error this
+				 * should be possible only in non-strict case.
+				 */
+				Assert(!strict);
+
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+									  errmsg("missing time-zone in input string for type timetz"))));
 			}
 
 			if (tm2timetz(&tm, fsec, *tz, result) != 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-						 errmsg("timetz out of range")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+									  errmsg("timetz out of range"))));
 
 			AdjustTimeForTypmod(&result->time, *typmod);
 
@@ -4065,9 +4217,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 			TimeADT		result;
 
 			if (tm2time(&tm, fsec, &result) != 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-						 errmsg("time out of range")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+									  errmsg("time out of range"))));
 
 			AdjustTimeForTypmod(&result, *typmod);
 
@@ -4077,11 +4229,12 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 	}
 	else
 	{
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("datetime format is not dated and not timed")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("datetime format is not dated and not timed"))));
 	}
 
+on_error:
 	return (Datum) 0;
 }
 
@@ -4102,16 +4255,19 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
  *
  * 'strict' enables error reporting on unmatched trailing characters in input or
  * format strings patterns.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
-do_to_timestamp(text *date_txt, text *fmt, bool strict,
-				struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags)
+do_to_timestamp(text *date_txt, text *fmt, bool strict, struct pg_tm *tm,
+				fsec_t *fsec, int *fprec, int *flags, bool *have_error)
 {
-	FormatNode *format;
+	FormatNode *format = NULL;
 	TmFromChar	tmfc;
 	int			fmt_len;
 	char	   *date_str;
 	int			fmask;
+	bool		incache = false;
 
 	date_str = text_to_cstring(date_txt);
 
@@ -4125,7 +4281,6 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 	if (fmt_len)
 	{
 		char	   *fmt_str;
-		bool		incache;
 
 		fmt_str = text_to_cstring(fmt);
 
@@ -4135,8 +4290,6 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 			 * Allocate new memory if format picture is bigger than static
 			 * cache and do not use cache (call parser always)
 			 */
-			incache = false;
-
 			format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode));
 
 			parse_format(format, fmt_str, DCH_keywords,
@@ -4158,15 +4311,21 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 		/* dump_index(DCH_keywords, DCH_index); */
 #endif
 
-		DCH_from_char(format, date_str, &tmfc, strict);
+		DCH_from_char(format, date_str, &tmfc, strict, have_error);
+		CHECK_ERROR;
 
 		pfree(fmt_str);
 
 		if (flags)
-			*flags = DCH_datetime_type(format);
+			*flags = DCH_datetime_type(format, have_error);
 
 		if (!incache)
+		{
 			pfree(format);
+			format = NULL;
+		}
+
+		CHECK_ERROR;
 	}
 
 	DEBUG_TMFC(&tmfc);
@@ -4195,11 +4354,13 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 	if (tmfc.clock == CLOCK_12_HOUR)
 	{
 		if (tm->tm_hour < 1 || tm->tm_hour > HOURS_PER_DAY / 2)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("hour \"%d\" is invalid for the 12-hour clock",
-							tm->tm_hour),
-					 errhint("Use the 24-hour clock, or give an hour between 1 and 12.")));
+		{
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("hour \"%d\" is invalid for the 12-hour clock",
+										 tm->tm_hour),
+								  errhint("Use the 24-hour clock, or give an hour between 1 and 12."))));
+		}
 
 		if (tmfc.pm && tm->tm_hour < HOURS_PER_DAY / 2)
 			tm->tm_hour += HOURS_PER_DAY / 2;
@@ -4303,9 +4464,11 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 		 */
 
 		if (!tm->tm_year && !tmfc.bc)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("cannot calculate day of year without year information")));
+		{
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("cannot calculate day of year without year information"))));
+		}
 
 		if (tmfc.mode == FROM_CHAR_DATE_ISOWEEK)
 		{
@@ -4362,7 +4525,7 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 			 * said DTERR_MD_FIELD_OVERFLOW, because we don't want to print an
 			 * irrelevant hint about datestyle.
 			 */
-			DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp");
+			RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp"));
 		}
 	}
 
@@ -4371,7 +4534,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 		tm->tm_min < 0 || tm->tm_min >= MINS_PER_HOUR ||
 		tm->tm_sec < 0 || tm->tm_sec >= SECS_PER_MINUTE ||
 		*fsec < INT64CONST(0) || *fsec >= USECS_PER_SEC)
-		DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp");
+	{
+		RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp"));
+	}
 
 	/* Save parsed time-zone into tm->tm_zone if it was specified */
 	if (tmfc.tzsign)
@@ -4380,7 +4545,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 
 		if (tmfc.tzh < 0 || tmfc.tzh > MAX_TZDISP_HOUR ||
 			tmfc.tzm < 0 || tmfc.tzm >= MINS_PER_HOUR)
-			DateTimeParseError(DTERR_TZDISP_OVERFLOW, date_str, "timestamp");
+		{
+			RETURN_ERROR(DateTimeParseError(DTERR_TZDISP_OVERFLOW, date_str, "timestamp"));
+		}
 
 		tz = psprintf("%c%02d:%02d",
 					  tmfc.tzsign > 0 ? '+' : '-', tmfc.tzh, tmfc.tzm);
@@ -4390,6 +4557,11 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 
 	DEBUG_TM(tm);
 
+on_error:
+
+	if (format && !incache)
+		pfree(format);
+
 	pfree(date_str);
 }
 
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 2931bd58c0d..80b8d656f2f 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -329,11 +329,11 @@ timestamp_scale(PG_FUNCTION_ARGS)
 }
 
 /*
- * AdjustTimestampForTypmod --- round off a timestamp to suit given typmod
+ * AdjustTimestampForTypmodError --- round off a timestamp to suit given typmod
  * Works for either timestamp or timestamptz.
  */
-void
-AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
+bool
+AdjustTimestampForTypmodError(Timestamp *time, int32 typmod, bool *error)
 {
 	static const int64 TimestampScales[MAX_TIMESTAMP_PRECISION + 1] = {
 		INT64CONST(1000000),
@@ -359,10 +359,18 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
 		&& (typmod != -1) && (typmod != MAX_TIMESTAMP_PRECISION))
 	{
 		if (typmod < 0 || typmod > MAX_TIMESTAMP_PRECISION)
+		{
+			if (error)
+			{
+				*error = true;
+				return false;
+			}
+
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("timestamp(%d) precision must be between %d and %d",
 							typmod, 0, MAX_TIMESTAMP_PRECISION)));
+		}
 
 		if (*time >= INT64CONST(0))
 		{
@@ -375,8 +383,15 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
 					  * TimestampScales[typmod]);
 		}
 	}
+
+	return true;
 }
 
+void
+AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
+{
+	(void) AdjustTimestampForTypmodError(time, typmod, NULL);
+}
 
 /* timestamptz_in()
  * Convert a string to internal form.
@@ -5172,8 +5187,15 @@ timestamp_timestamptz(PG_FUNCTION_ARGS)
 	PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
 }
 
-static TimestampTz
-timestamp2timestamptz(Timestamp timestamp)
+/*
+ * Convert timestamp to timestamp with time zone.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero returned.
+ */
+
+TimestampTz
+timestamp2timestamptz_opt_error(Timestamp timestamp, bool *have_error)
 {
 	TimestampTz result;
 	struct pg_tm tt,
@@ -5182,23 +5204,33 @@ timestamp2timestamptz(Timestamp timestamp)
 	int			tz;
 
 	if (TIMESTAMP_NOT_FINITE(timestamp))
-		result = timestamp;
-	else
-	{
-		if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("timestamp out of range")));
+		return timestamp;
 
+	if (!timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL))
+	{
 		tz = DetermineTimeZoneOffset(tm, session_timezone);
 
-		if (tm2timestamp(tm, fsec, &tz, &result) != 0)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("timestamp out of range")));
+		if (!tm2timestamp(tm, fsec, &tz, &result))
+			return result;
 	}
 
-	return result;
+	if (have_error)
+		*have_error = true;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+				 errmsg("timestamp out of range")));
+
+	return 0;
+}
+
+/*
+ * Single-argument version of timestamp2timestamptz_opt_error().
+ */
+static TimestampTz
+timestamp2timestamptz(Timestamp timestamp)
+{
+	return timestamp2timestamptz_opt_error(timestamp, NULL);
 }
 
 /* timestamptz_timestamp()
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index bd15bfa5bb0..c29f13aaf04 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -70,6 +70,8 @@ typedef struct
 /* date.c */
 extern int32 anytime_typmod_check(bool istz, int32 typmod);
 extern double date2timestamp_no_overflow(DateADT dateVal);
+extern Timestamp date2timestamp_opt_error(DateADT dateVal, bool *have_error);
+extern TimestampTz date2timestamptz_opt_error(DateADT dateVal, bool *have_error);
 extern void EncodeSpecialDate(DateADT dt, char *str);
 extern DateADT GetSQLCurrentDate(void);
 extern TimeTzADT *GetSQLCurrentTime(int32 typmod);
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index 0cafdd26538..5ebf336071b 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -337,5 +337,7 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
 extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
 
 extern void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
+extern bool AdjustTimestampForTypmodError(Timestamp *time, int32 typmod,
+										  bool *error);
 
 #endif							/* DATETIME_H */
diff --git a/src/include/utils/formatting.h b/src/include/utils/formatting.h
index a390302e89c..1a2e2a9caa8 100644
--- a/src/include/utils/formatting.h
+++ b/src/include/utils/formatting.h
@@ -29,6 +29,7 @@ extern char *asc_toupper(const char *buff, size_t nbytes);
 extern char *asc_initcap(const char *buff, size_t nbytes);
 
 extern Datum parse_datetime(text *date_txt, text *fmt, bool strict,
-							Oid *typid, int32 *typmod, int *tz);
+							Oid *typid, int32 *typmod, int *tz,
+							bool *have_error);
 
 #endif
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index ea16190ec3d..e884d4405f5 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -97,6 +97,9 @@ extern int	timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
 /* timestamp comparison works for timestamptz also */
 #define timestamptz_cmp_internal(dt1,dt2)	timestamp_cmp_internal(dt1, dt2)
 
+extern TimestampTz timestamp2timestamptz_opt_error(Timestamp timestamp,
+												   bool *have_error);
+
 extern int	isoweek2j(int year, int week);
 extern void isoweek2date(int woy, int *year, int *mon, int *mday);
 extern void isoweekdate2date(int isoweek, int wday, int *year, int *mon, int *mday);
0004-implement-jsonpath-datetime-4.patchapplication/octet-stream; name=0004-implement-jsonpath-datetime-4.patchDownload
commit 1466a82e56a09ac236fe9e766c95393d520aedb9
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Mon May 27 10:33:09 2019 +0300

    Implement jsonpath .datetime() method
    
    This commit implements jsonpath .datetime() method as it's specified in
    SQL/JSON standard.  There are no-argument and single-argument versions of
    this method.  No-argument version selects first of ISO datetime formats
    matching input string.  Single-argument version accepts template string as
    its argument.
    
    Additionally to .datetime() method itself this commit also implements
    comparison ability of resulting date and time values.  There is some difficulty
    because exising jsonb_path_*() functions are immutable, while comparison of
    timezoned and non-timezoned types involves current timezone.  At first, current
    timezone could be changes in session.  Moreover, timezones themselves are not
    immutable and could be updated.  This is why we let existing immutable functions
    throw errors on such non-immutable comparison.  In the same time this commit
    provides jsonb_path_*_tz() functions which are stable and support operations
    involving timezones.

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index afde81664bb..fdc02f61909 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -11744,16 +11744,6 @@ table2-mapping
   </para>
 
   <itemizedlist>
-   <listitem>
-    <para>
-     <literal>.datetime()</literal> item method is not implemented yet
-     mainly because immutable <type>jsonpath</type> functions and operators
-     cannot reference session timezone, which is used in some datetime
-     operations.  Datetime support will be added to <type>jsonpath</type>
-     in future versions of <productname>PostgreSQL</productname>.
-    </para>
-   </listitem>
-
    <listitem>
     <para>
      A path expression can be a Boolean predicate, although the SQL/JSON
@@ -11959,6 +11949,20 @@ table2-mapping
         <entry><literal>$.z.abs()</literal></entry>
         <entry><literal>0.3</literal></entry>
        </row>
+       <row>
+        <entry><literal>datetime()</literal></entry>
+        <entry>Datetime value converted from a string</entry>
+        <entry><literal>["2015-8-1", "2015-08-12"]</literal></entry>
+        <entry><literal>$[*] ? (@.datetime() &lt; "2015-08-2". datetime())</literal></entry>
+        <entry><literal>2015-8-1</literal></entry>
+       </row>
+       <row>
+        <entry><literal>datetime(<replaceable>template</replaceable>)</literal></entry>
+        <entry>Datetime value converted from a string with a specified template</entry>
+        <entry><literal>["12:30", "18:40"]</literal></entry>
+        <entry><literal>$[*].datetime("HH24:MI")</literal></entry>
+        <entry><literal>"12:30:00", "18:40:00"</literal></entry>
+       </row>
        <row>
         <entry><literal>keyvalue()</literal></entry>
         <entry>
@@ -11976,6 +11980,27 @@ table2-mapping
      </tgroup>
     </table>
 
+    <note>
+     <para>
+      Result type of <literal>datetime()</literal> and
+      <literal>datetime(<replaceable>template</replaceable>)</literal>
+      methods is determined dynamically between <literal>date</literal>,
+      <literal>timetz</literal>, <literal>time</literal>,
+      <literal>timestamptz</literal> and <literal>timestamp</literal>.
+     </para>
+     <para>
+      <literal>datetime()</literal> method sequentially tries ISO formats for
+      <literal>date</literal>, <literal>timetz</literal>,
+      <literal>time</literal> <literal>timestamptz</literal> and
+      <literal>timestamp</literal>.  It stops on first matching format and
+      corresponding datatype.
+     </para>
+     <para>
+      <literal>datetime(<replaceable>template</replaceable>)</literal> method
+      determines result type by template string.
+     </para>
+    </note>
+
     <table id="functions-sqljson-filter-ex-table">
      <title><type>jsonpath</type> Filter Expression Elements</title>
      <tgroup cols="5">
@@ -12118,6 +12143,23 @@ table2-mapping
       </tbody>
      </tgroup>
     </table>
+
+    <note>
+     <para>
+      When different datetime values are compared, automatic cast is applied.
+      <literal>date</literal> may be cast to <literal>timestamp</literal>,
+      <literal>date</literal> <literal>timestamp</literal> to <literal>timestamptz</literal>,
+      <literal>time</literal> to <literal>timetz</literal>.
+     </para>
+     <para>
+      For <literal>datetime()</literal> method result type is determined by
+      matching ISO format.
+     </para>
+     <para>
+      For <literal>datetime(<replaceable>template</replaceable>)</literal>
+      method result type is determined by template string.
+     </para>
+    </note>
    </sect3>
 
   </sect2>
@@ -12351,7 +12393,7 @@ table2-mapping
    <para>
     The <literal>@?</literal> and <literal>@@</literal> operators suppress
     the following errors: lacking object field or array element, unexpected
-    JSON item type, and numeric errors.
+    JSON item type, datetime and numeric errors.
     This behavior might be helpful while searching over JSON document
     collections of varying structure.
    </para>
@@ -12620,18 +12662,33 @@ table2-mapping
   <indexterm>
    <primary>jsonb_path_exists</primary>
   </indexterm>
+  <indexterm>
+   <primary>jsonb_path_exists_tz</primary>
+  </indexterm>
   <indexterm>
    <primary>jsonb_path_match</primary>
   </indexterm>
+  <indexterm>
+   <primary>jsonb_path_match_tz</primary>
+  </indexterm>
   <indexterm>
    <primary>jsonb_path_query</primary>
   </indexterm>
+  <indexterm>
+   <primary>jsonb_path_query_tz</primary>
+  </indexterm>
   <indexterm>
    <primary>jsonb_path_query_array</primary>
   </indexterm>
+  <indexterm>
+   <primary>jsonb_path_query_array_tz</primary>
+  </indexterm>
   <indexterm>
    <primary>jsonb_path_query_first</primary>
   </indexterm>
+  <indexterm>
+   <primary>jsonb_path_query_first_tz</primary>
+  </indexterm>
 
   <table id="functions-json-processing-table">
     <title>JSON Processing Functions</title>
@@ -12971,6 +13028,9 @@ table2-mapping
          <para><literal>
            jsonb_path_exists(target jsonb, path jsonpath [, vars jsonb, silent bool])
          </literal></para>
+         <para><literal>
+           jsonb_path_exists_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>boolean</type></entry>
         <entry>
@@ -12991,6 +13051,9 @@ table2-mapping
          <para><literal>
            jsonb_path_match(target jsonb, path jsonpath [, vars jsonb, silent bool])
          </literal></para>
+         <para><literal>
+           jsonb_path_match_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>boolean</type></entry>
         <entry>
@@ -13013,6 +13076,9 @@ table2-mapping
          <para><literal>
            jsonb_path_query(target jsonb, path jsonpath [, vars jsonb, silent bool])
          </literal></para>
+         <para><literal>
+           jsonb_path_query_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>setof jsonb</type></entry>
         <entry>
@@ -13041,6 +13107,9 @@ table2-mapping
          <para><literal>
            jsonb_path_query_array(target jsonb, path jsonpath [, vars jsonb, silent bool])
          </literal></para>
+         <para><literal>
+           jsonb_path_query_array_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>jsonb</type></entry>
         <entry>
@@ -13061,6 +13130,9 @@ table2-mapping
          <para><literal>
            jsonb_path_query_first(target jsonb, path jsonpath [, vars jsonb, silent bool])
          </literal></para>
+         <para><literal>
+           jsonb_path_query_first_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>jsonb</type></entry>
         <entry>
@@ -13203,11 +13275,8 @@ table2-mapping
 
   <note>
    <para>
-    The <literal>jsonb_path_exists</literal>, <literal>jsonb_path_match</literal>,
-    <literal>jsonb_path_query</literal>, <literal>jsonb_path_query_array</literal> and
-    <literal>jsonb_path_query_first</literal>
-    functions have optional <literal>vars</literal> and <literal>silent</literal>
-    arguments.
+    The <literal>jsonb_path_*</literal> functions have optional
+    <literal>vars</literal> and <literal>silent</literal> arguments.
    </para>
    <para>
     If the <literal>vars</literal> argument is specified, it provides an
@@ -13221,6 +13290,24 @@ table2-mapping
    </para>
   </note>
 
+  <note>
+   <para>
+    Some of <literal>jsonb_path_*</literal> functions have
+    a <literal>_tz</literal> suffix.
+   </para>
+   <para>
+    Functions with <literal>_tz</literal> suffix support all SQL/JSON path
+    features including timezone dependent datetime casts.  Timezones are
+    not immutable, therefore these functions are qualified as stable and
+    can't be used in expression indexes (<xref linkend="indexes-expressional"/>).
+   </para>
+   <para>
+    Functions without <literal>_tz</literal> suffix are lacking of timezone
+    dependent datetime casts.  Thanks to that these functions are qualified
+    as immutable and can be used in expression indexes.
+   </para>
+  </note>
+
   <para>
     See also <xref linkend="functions-aggregate"/> for the aggregate
     function <function>json_agg</function> which aggregates record
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ea4c85e3959..423ae4fdc8a 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1287,6 +1287,46 @@ LANGUAGE INTERNAL
 STRICT IMMUTABLE PARALLEL SAFE
 AS 'jsonb_path_query_first';
 
+CREATE OR REPLACE FUNCTION
+  jsonb_path_exists_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                    silent boolean DEFAULT false)
+RETURNS boolean
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_exists_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_match_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                   silent boolean DEFAULT false)
+RETURNS boolean
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_match_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_query_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                   silent boolean DEFAULT false)
+RETURNS SETOF jsonb
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_query_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_query_array_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                         silent boolean DEFAULT false)
+RETURNS jsonb
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_query_array_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_query_first_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                         silent boolean DEFAULT false)
+RETURNS jsonb
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_query_first_tz';
+
 --
 -- The default permissions for functions mean that anyone can execute them.
 -- A number of functions shouldn't be executable by just anyone, but rather
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 87ae60e490f..65c047acc86 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -337,12 +337,14 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
 		case jpiPlus:
 		case jpiMinus:
 		case jpiExists:
+		case jpiDatetime:
 			{
 				int32		arg = reserveSpaceForItemPointer(buf);
 
-				chld = flattenJsonPathParseItem(buf, item->value.arg,
-												nestingLevel + argNestingLevel,
-												insideArraySubscript);
+				chld = !item->value.arg ? pos :
+					flattenJsonPathParseItem(buf, item->value.arg,
+											 nestingLevel + argNestingLevel,
+											 insideArraySubscript);
 				*(int32 *) (buf->data + arg) = chld - pos;
 			}
 			break;
@@ -692,6 +694,15 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
 		case jpiDouble:
 			appendBinaryStringInfo(buf, ".double()", 9);
 			break;
+		case jpiDatetime:
+			appendBinaryStringInfo(buf, ".datetime(", 10);
+			if (v->content.arg)
+			{
+				jspGetArg(v, &elem);
+				printJsonPathItem(buf, &elem, false, false);
+			}
+			appendStringInfoChar(buf, ')');
+			break;
 		case jpiKeyValue:
 			appendBinaryStringInfo(buf, ".keyvalue()", 11);
 			break;
@@ -754,6 +765,8 @@ jspOperationName(JsonPathItemType type)
 			return "floor";
 		case jpiCeiling:
 			return "ceiling";
+		case jpiDatetime:
+			return "datetime";
 		default:
 			elog(ERROR, "unrecognized jsonpath item type: %d", type);
 			return NULL;
@@ -889,6 +902,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
 		case jpiPlus:
 		case jpiMinus:
 		case jpiFilter:
+		case jpiDatetime:
 			read_int32(v->content.arg, base, pos);
 			break;
 		case jpiIndexArray:
@@ -913,7 +927,8 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a)
 		   v->type == jpiIsUnknown ||
 		   v->type == jpiExists ||
 		   v->type == jpiPlus ||
-		   v->type == jpiMinus);
+		   v->type == jpiMinus ||
+		   v->type == jpiDatetime);
 
 	jspInitByBuffer(a, v->base, v->content.arg);
 }
@@ -961,6 +976,7 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
 			   v->type == jpiFloor ||
 			   v->type == jpiCeiling ||
 			   v->type == jpiDouble ||
+			   v->type == jpiDatetime ||
 			   v->type == jpiKeyValue ||
 			   v->type == jpiStartsWith);
 
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index d8647f71af3..f3d3e09abf0 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -66,6 +66,7 @@
 #include "miscadmin.h"
 #include "regex/regex.h"
 #include "utils/builtins.h"
+#include "utils/datetime.h"
 #include "utils/datum.h"
 #include "utils/formatting.h"
 #include "utils/float.h"
@@ -107,6 +108,7 @@ typedef struct JsonPathExecContext
 										 * ignored */
 	bool		throwErrors;	/* with "false" all suppressible errors are
 								 * suppressed */
+	bool		useTz;
 } JsonPathExecContext;
 
 /* Context for LIKE_REGEX execution. */
@@ -173,7 +175,8 @@ typedef JsonPathBool (*JsonPathPredicateCallback) (JsonPathItem *jsp,
 typedef Numeric (*BinaryArithmFunc) (Numeric num1, Numeric num2, bool *error);
 
 static JsonPathExecResult executeJsonPath(JsonPath *path, Jsonb *vars,
-										  Jsonb *json, bool throwErrors, JsonValueList *result);
+										  Jsonb *json, bool throwErrors,
+										  JsonValueList *result, bool useTz);
 static JsonPathExecResult executeItem(JsonPathExecContext *cxt,
 									  JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
 static JsonPathExecResult executeItemOptUnwrapTarget(JsonPathExecContext *cxt,
@@ -216,6 +219,8 @@ static JsonPathBool executeLikeRegex(JsonPathItem *jsp, JsonbValue *str,
 static JsonPathExecResult executeNumericItemMethod(JsonPathExecContext *cxt,
 												   JsonPathItem *jsp, JsonbValue *jb, bool unwrap, PGFunction func,
 												   JsonValueList *found);
+static JsonPathExecResult executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
+												JsonbValue *jb, JsonValueList *found);
 static JsonPathExecResult executeKeyValueMethod(JsonPathExecContext *cxt,
 												JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
 static JsonPathExecResult appendBoolResult(JsonPathExecContext *cxt,
@@ -227,7 +232,8 @@ static void getJsonPathVariable(JsonPathExecContext *cxt,
 static int	JsonbArraySize(JsonbValue *jb);
 static JsonPathBool executeComparison(JsonPathItem *cmp, JsonbValue *lv,
 									  JsonbValue *rv, void *p);
-static JsonPathBool compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2);
+static JsonPathBool compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2,
+								 bool useTz);
 static int	compareNumeric(Numeric a, Numeric b);
 static JsonbValue *copyJsonbValue(JsonbValue *src);
 static JsonPathExecResult getArrayIndex(JsonPathExecContext *cxt,
@@ -248,6 +254,8 @@ static JsonbValue *JsonbInitBinary(JsonbValue *jbv, Jsonb *jb);
 static int	JsonbType(JsonbValue *jb);
 static JsonbValue *getScalar(JsonbValue *scalar, enum jbvType type);
 static JsonbValue *wrapItemsInArray(const JsonValueList *items);
+static int	compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
+							bool useTz, bool *have_error);
 
 /****************** User interface to JsonPath executor ********************/
 
@@ -263,8 +271,8 @@ static JsonbValue *wrapItemsInArray(const JsonValueList *items);
  *		SQL/JSON.  Regarding jsonb_path_match(), this function doesn't have
  *		an analogy in SQL/JSON, so we define its behavior on our own.
  */
-Datum
-jsonb_path_exists(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_exists_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -278,7 +286,7 @@ jsonb_path_exists(PG_FUNCTION_ARGS)
 		silent = PG_GETARG_BOOL(3);
 	}
 
-	res = executeJsonPath(jp, vars, jb, !silent, NULL);
+	res = executeJsonPath(jp, vars, jb, !silent, NULL, tz);
 
 	PG_FREE_IF_COPY(jb, 0);
 	PG_FREE_IF_COPY(jp, 1);
@@ -289,6 +297,18 @@ jsonb_path_exists(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(res == jperOk);
 }
 
+Datum
+jsonb_path_exists(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_exists_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_exists_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_exists_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_exists_opr
  *		Implementation of operator "jsonb @? jsonpath" (2-argument version of
@@ -298,7 +318,7 @@ Datum
 jsonb_path_exists_opr(PG_FUNCTION_ARGS)
 {
 	/* just call the other one -- it can handle both cases */
-	return jsonb_path_exists(fcinfo);
+	return jsonb_path_exists_internal(fcinfo, false);
 }
 
 /*
@@ -306,8 +326,8 @@ jsonb_path_exists_opr(PG_FUNCTION_ARGS)
  *		Returns jsonpath predicate result item for the specified jsonb value.
  *		See jsonb_path_exists() comment for details regarding error handling.
  */
-Datum
-jsonb_path_match(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_match_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -321,7 +341,7 @@ jsonb_path_match(PG_FUNCTION_ARGS)
 		silent = PG_GETARG_BOOL(3);
 	}
 
-	(void) executeJsonPath(jp, vars, jb, !silent, &found);
+	(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 	PG_FREE_IF_COPY(jb, 0);
 	PG_FREE_IF_COPY(jp, 1);
@@ -345,6 +365,18 @@ jsonb_path_match(PG_FUNCTION_ARGS)
 	PG_RETURN_NULL();
 }
 
+Datum
+jsonb_path_match(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_match_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_match_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_match_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_match_opr
  *		Implementation of operator "jsonb @@ jsonpath" (2-argument version of
@@ -354,7 +386,7 @@ Datum
 jsonb_path_match_opr(PG_FUNCTION_ARGS)
 {
 	/* just call the other one -- it can handle both cases */
-	return jsonb_path_match(fcinfo);
+	return jsonb_path_match_internal(fcinfo, false);
 }
 
 /*
@@ -362,8 +394,8 @@ jsonb_path_match_opr(PG_FUNCTION_ARGS)
  *		Executes jsonpath for given jsonb document and returns result as
  *		rowset.
  */
-Datum
-jsonb_path_query(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_query_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	FuncCallContext *funcctx;
 	List	   *found;
@@ -387,7 +419,7 @@ jsonb_path_query(PG_FUNCTION_ARGS)
 		vars = PG_GETARG_JSONB_P_COPY(2);
 		silent = PG_GETARG_BOOL(3);
 
-		(void) executeJsonPath(jp, vars, jb, !silent, &found);
+		(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 		funcctx->user_fctx = JsonValueListGetList(&found);
 
@@ -408,13 +440,25 @@ jsonb_path_query(PG_FUNCTION_ARGS)
 	SRF_RETURN_NEXT(funcctx, JsonbPGetDatum(JsonbValueToJsonb(v)));
 }
 
+Datum
+jsonb_path_query(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_query_array
  *		Executes jsonpath for given jsonb document and returns result as
  *		jsonb array.
  */
-Datum
-jsonb_path_query_array(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_query_array_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -422,18 +466,30 @@ jsonb_path_query_array(PG_FUNCTION_ARGS)
 	Jsonb	   *vars = PG_GETARG_JSONB_P(2);
 	bool		silent = PG_GETARG_BOOL(3);
 
-	(void) executeJsonPath(jp, vars, jb, !silent, &found);
+	(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 	PG_RETURN_JSONB_P(JsonbValueToJsonb(wrapItemsInArray(&found)));
 }
 
+Datum
+jsonb_path_query_array(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_array_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_array_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_array_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_query_first
  *		Executes jsonpath for given jsonb document and returns first result
  *		item.  If there are no items, NULL returned.
  */
-Datum
-jsonb_path_query_first(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_query_first_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -441,7 +497,7 @@ jsonb_path_query_first(PG_FUNCTION_ARGS)
 	Jsonb	   *vars = PG_GETARG_JSONB_P(2);
 	bool		silent = PG_GETARG_BOOL(3);
 
-	(void) executeJsonPath(jp, vars, jb, !silent, &found);
+	(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 	if (JsonValueListLength(&found) >= 1)
 		PG_RETURN_JSONB_P(JsonbValueToJsonb(JsonValueListHead(&found)));
@@ -449,6 +505,18 @@ jsonb_path_query_first(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 }
 
+Datum
+jsonb_path_query_first(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_first_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_first_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_first_internal(fcinfo, true);
+}
+
 /********************Execute functions for JsonPath**************************/
 
 /*
@@ -472,7 +540,7 @@ jsonb_path_query_first(PG_FUNCTION_ARGS)
  */
 static JsonPathExecResult
 executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors,
-				JsonValueList *result)
+				JsonValueList *result, bool useTz)
 {
 	JsonPathExecContext cxt;
 	JsonPathExecResult res;
@@ -502,6 +570,7 @@ executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors,
 	cxt.lastGeneratedObjectId = vars ? 2 : 1;
 	cxt.innermostArraySize = -1;
 	cxt.throwErrors = throwErrors;
+	cxt.useTz = useTz;
 
 	if (jspStrictAbsenseOfErrors(&cxt) && !result)
 	{
@@ -1030,6 +1099,12 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
 			}
 			break;
 
+		case jpiDatetime:
+			if (unwrap && JsonbType(jb) == jbvArray)
+				return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
+
+			return executeDateTimeMethod(cxt, jsp, jb, found);
+
 		case jpiKeyValue:
 			if (unwrap && JsonbType(jb) == jbvArray)
 				return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
@@ -1216,7 +1291,7 @@ executeBoolItem(JsonPathExecContext *cxt, JsonPathItem *jsp,
 			jspGetLeftArg(jsp, &larg);
 			jspGetRightArg(jsp, &rarg);
 			return executePredicate(cxt, jsp, &larg, &rarg, jb, true,
-									executeComparison, NULL);
+									executeComparison, cxt);
 
 		case jpiStartsWith:		/* 'whole STARTS WITH initial' */
 			jspGetLeftArg(jsp, &larg);	/* 'whole' */
@@ -1719,6 +1794,138 @@ executeNumericItemMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
 	return executeNextItem(cxt, jsp, &next, jb, found, false);
 }
 
+/*
+ * Implementation of .datetime() method.
+ *
+ * Parses string as datetime value.  Particular type is determined runtime.
+ * When argument is provided then it's used as template string.  When no
+ * argument is supplied, first fitting ISO format is selected.
+ */
+static JsonPathExecResult
+executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
+					  JsonbValue *jb, JsonValueList *found)
+{
+	JsonbValue	jbvbuf;
+	Datum		value;
+	text	   *datetime;
+	Oid			typid;
+	int32		typmod = -1;
+	int			tz = 0;
+	bool		hasNext;
+	JsonPathExecResult res = jperNotFound;
+	JsonPathItem elem;
+
+	if (!(jb = getScalar(jb, jbvString)))
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+							  errmsg("invalid argument for SQL/JSON datetime function"),
+							  errdetail("jsonpath item method .%s() is applied to not a string",
+										jspOperationName(jsp->type)))));
+
+	datetime = cstring_to_text_with_len(jb->val.string.val,
+										jb->val.string.len);
+
+	if (jsp->content.arg)
+	{
+		text	   *template;
+		char	   *template_str;
+		int			template_len;
+		bool		have_error = false;
+
+		jspGetArg(jsp, &elem);
+
+		if (elem.type != jpiString)
+			elog(ERROR, "invalid jsonpath item type for .datetime() argument");
+
+		template_str = jspGetString(&elem, &template_len);
+
+		template = cstring_to_text_with_len(template_str,
+											template_len);
+
+		value = parse_datetime(datetime, template, true,
+							   &typid, &typmod, &tz,
+							   jspThrowErrors(cxt) ? NULL : &have_error);
+
+		if (have_error)
+			res = jperError;
+		else
+			res = jperOk;
+	}
+	else
+	{
+		/*
+		 * According to SQL/JSON standard enumerate ISO formats for: date,
+		 * timetz, time, timestamptz, timestamp.
+		 */
+		static const char *fmt_str[] =
+		{
+			"yyyy-mm-dd",
+			"HH24:MI:SS TZH:TZM",
+			"HH24:MI:SS TZH",
+			"HH24:MI:SS",
+			"yyyy-mm-dd HH24:MI:SS TZH:TZM",
+			"yyyy-mm-dd HH24:MI:SS TZH",
+			"yyyy-mm-dd HH24:MI:SS"
+		};
+
+		/* cache for format texts */
+		static text *fmt_txt[lengthof(fmt_str)] = {0};
+		int			i;
+
+		/* loop until datetime format fits */
+		for (i = 0; i < lengthof(fmt_str); i++)
+		{
+			bool		have_error = false;
+
+			if (!fmt_txt[i])
+			{
+				MemoryContext oldcxt =
+				MemoryContextSwitchTo(TopMemoryContext);
+
+				fmt_txt[i] = cstring_to_text(fmt_str[i]);
+				MemoryContextSwitchTo(oldcxt);
+			}
+
+			value = parse_datetime(datetime, fmt_txt[i], true,
+								   &typid, &typmod, &tz,
+								   &have_error);
+
+			if (!have_error)
+			{
+				res = jperOk;
+				break;
+			}
+		}
+
+		if (res == jperNotFound)
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+								  errmsg("invalid argument for SQL/JSON datetime function"),
+								  errdetail("unrecognized datetime format"),
+								  errhint("use datetime template argument for explicit format specification"))));
+	}
+
+	pfree(datetime);
+
+	if (jperIsError(res))
+		return res;
+
+	hasNext = jspGetNext(jsp, &elem);
+
+	if (!hasNext && !found)
+		return res;
+
+	jb = hasNext ? &jbvbuf : palloc(sizeof(*jb));
+
+	jb->type = jbvDatetime;
+	jb->val.datetime.value = value;
+	jb->val.datetime.typid = typid;
+	jb->val.datetime.typmod = typmod;
+	jb->val.datetime.tz = tz;
+
+	return executeNextItem(cxt, jsp, &elem, jb, found, hasNext);
+}
+
 /*
  * Implementation of .keyvalue() method.
  *
@@ -1979,14 +2186,16 @@ JsonbArraySize(JsonbValue *jb)
 static JsonPathBool
 executeComparison(JsonPathItem *cmp, JsonbValue *lv, JsonbValue *rv, void *p)
 {
-	return compareItems(cmp->type, lv, rv);
+	JsonPathExecContext *cxt = (JsonPathExecContext *) p;
+
+	return compareItems(cmp->type, lv, rv, cxt->useTz);
 }
 
 /*
  * Compare two SQL/JSON items using comparison operation 'op'.
  */
 static JsonPathBool
-compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2)
+compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2, bool useTz)
 {
 	int			cmp;
 	bool		res;
@@ -2028,6 +2237,21 @@ compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2)
 							 jb2->val.string.val, jb2->val.string.len,
 							 DEFAULT_COLLATION_OID);
 			break;
+		case jbvDatetime:
+			{
+				bool		have_error = false;
+
+				cmp = compareDatetime(jb1->val.datetime.value,
+									  jb1->val.datetime.typid,
+									  jb2->val.datetime.value,
+									  jb2->val.datetime.typid,
+									  useTz,
+									  &have_error);
+
+				if (have_error)
+					return jpbUnknown;
+			}
+			break;
 
 		case jbvBinary:
 		case jbvArray:
@@ -2287,3 +2511,205 @@ wrapItemsInArray(const JsonValueList *items)
 
 	return pushJsonbValue(&ps, WJB_END_ARRAY, NULL);
 }
+
+/*
+ * Cross-type comparison of two datetime SQL/JSON items.  If items are
+ * uncomparable, 'error' flag is set.
+ */
+static int
+compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
+				bool useTz, bool *have_error)
+{
+	PGFunction cmpfunc = NULL;
+
+	switch (typid1)
+	{
+		case DATEOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					cmpfunc = date_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					val1 = date2timestamp_opt_error(val1, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("can't convert value from %s to %s without timezone usage",
+										"date", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val1 = date2timestamptz_opt_error(val1, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMEOID:
+			switch (typid2)
+			{
+				case TIMEOID:
+					cmpfunc = time_cmp;
+
+					break;
+
+				case TIMETZOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("can't convert value from %s to %s without timezone usage",
+										"time", "timetz"),
+								 errhint("use *_tz() function for timezone support")));
+					val1 = DirectFunctionCall1(time_timetz, val1);
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case DATEOID:
+				case TIMESTAMPOID:
+				case TIMESTAMPTZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMETZOID:
+			switch (typid2)
+			{
+				case TIMEOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("can't convert value from %s to %s without timezone usage",
+										"time", "timetz"),
+								 errhint("use *_tz() function for timezone support")));
+					val2 = DirectFunctionCall1(time_timetz, val2);
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case TIMETZOID:
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case DATEOID:
+				case TIMESTAMPOID:
+				case TIMESTAMPTZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMESTAMPOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					val2 = date2timestamp_opt_error(val2, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("can't convert value from %s to %s without timezone usage",
+										"timestamp", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val1 = timestamp2timestamptz_opt_error(val1, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMESTAMPTZOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("can't convert value from %s to %s without timezone usage",
+										"date", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val2 = date2timestamptz_opt_error(val2, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("can't convert value from %s to %s without timezone usage",
+										"timestamp", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val2 = timestamp2timestamptz_opt_error(val2, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		default:
+			elog(ERROR, "unrecognized SQL/JSON datetime type oid: %d",
+				 typid1);
+	}
+
+	if (*have_error)
+		return 0;
+
+	if (!cmpfunc)
+		elog(ERROR, "unrecognized SQL/JSON datetime type oid: %d",
+			 typid2);
+
+	*have_error = false;
+
+	return DatumGetInt32(DirectFunctionCall2(cmpfunc, val1, val2));
+}
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
index 91b4b2f5985..059faeeb2c7 100644
--- a/src/backend/utils/adt/jsonpath_gram.y
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -94,12 +94,14 @@ static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr,
 %token	<str>		LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
 %token	<str>		ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P
 %token	<str>		ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P KEYVALUE_P
+%token	<str>		DATETIME_P
 
 %type	<result>	result
 
 %type	<value>		scalar_value path_primary expr array_accessor
 					any_path accessor_op key predicate delimited_predicate
 					index_elem starts_with_initial expr_or_predicate
+					datetime_template opt_datetime_template
 
 %type	<elems>		accessor_expr
 
@@ -247,9 +249,20 @@ accessor_op:
 	| array_accessor				{ $$ = $1; }
 	| '.' any_path					{ $$ = $2; }
 	| '.' method '(' ')'			{ $$ = makeItemType($2); }
+	| '.' DATETIME_P '(' opt_datetime_template ')'
+									{ $$ = makeItemUnary(jpiDatetime, $4); }
 	| '?' '(' predicate ')'			{ $$ = makeItemUnary(jpiFilter, $3); }
 	;
 
+datetime_template:
+	STRING_P						{ $$ = makeItemString(&$1); }
+	;
+
+opt_datetime_template:
+	datetime_template				{ $$ = $1; }
+	| /* EMPTY */					{ $$ = NULL; }
+	;
+
 key:
 	key_name						{ $$ = makeItemKey(&$1); }
 	;
@@ -272,6 +285,7 @@ key_name:
 	| FLOOR_P
 	| DOUBLE_P
 	| CEILING_P
+	| DATETIME_P
 	| KEYVALUE_P
 	| LAST_P
 	| STARTS_P
diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l
index 2165ffcc254..ced65edb59b 100644
--- a/src/backend/utils/adt/jsonpath_scan.l
+++ b/src/backend/utils/adt/jsonpath_scan.l
@@ -340,6 +340,7 @@ static const JsonPathKeyword keywords[] = {
 	{ 6, false,	STRICT_P,	"strict"},
 	{ 7, false,	CEILING_P,	"ceiling"},
 	{ 7, false,	UNKNOWN_P,	"unknown"},
+	{ 8, false,	DATETIME_P,	"datetime"},
 	{ 8, false,	KEYVALUE_P,	"keyvalue"},
 	{ 10,false, LIKE_REGEX_P, "like_regex"},
 };
diff --git a/src/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index 16f5ca233a9..72876533acf 100644
--- a/src/backend/utils/errcodes.txt
+++ b/src/backend/utils/errcodes.txt
@@ -207,6 +207,7 @@ Section: Class 22 - Data Exception
 2200S    E    ERRCODE_INVALID_XML_COMMENT                                    invalid_xml_comment
 2200T    E    ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION                     invalid_xml_processing_instruction
 22030    E    ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE                        duplicate_json_object_key_value
+22031    E    ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION            invalid_argument_for_json_datetime_function
 22032    E    ERRCODE_INVALID_JSON_TEXT                                      invalid_json_text
 22033    E    ERRCODE_INVALID_JSON_SUBSCRIPT                                 invalid_json_subscript
 22034    E    ERRCODE_MORE_THAN_ONE_JSON_ITEM                                more_than_one_json_item
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0902dce5f14..4acdd7a3956 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9323,6 +9323,28 @@
   proname => 'jsonb_path_match', prorettype => 'bool',
   proargtypes => 'jsonb jsonpath jsonb bool', prosrc => 'jsonb_path_match' },
 
+{ oid => '6015', descr => 'jsonpath exists test with timezone',
+  proname => 'jsonb_path_exists_tz', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_exists_tz' },
+{ oid => '6016', descr => 'jsonpath query with timezone',
+  proname => 'jsonb_path_query_tz', provolatile => 's',
+  prorows => '1000', proretset => 't',
+  prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_query_tz' },
+{ oid => '6017', descr => 'jsonpath query wrapped into array with timezone',
+  proname => 'jsonb_path_query_array_tz', provolatile => 's',
+  prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_query_array_tz' },
+{ oid => '6018', descr => 'jsonpath query first item with timezone',
+  proname => 'jsonb_path_query_first_tz', provolatile => 's',
+  prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_query_first_tz' },
+{ oid => '6019', descr => 'jsonpath match with timezone',
+  proname => 'jsonb_path_match_tz', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_match_tz' },
+
 { oid => '4010', descr => 'implementation of @? operator',
   proname => 'jsonb_path_exists_opr', prorettype => 'bool',
   proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_path_exists_opr' },
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 40ad5fda928..f6b17c8aa2f 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -79,6 +79,7 @@ typedef enum JsonPathItemType
 	jpiFloor,					/* .floor() item method */
 	jpiCeiling,					/* .ceiling() item method */
 	jpiDouble,					/* .double() item method */
+	jpiDatetime,				/* .datetime() item method */
 	jpiKeyValue,				/* .keyvalue() item method */
 	jpiSubscript,				/* array subscript: 'expr' or 'expr TO expr' */
 	jpiLast,					/* LAST array subscript */
diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
index 31a871af028..750802989be 100644
--- a/src/test/regress/expected/jsonb_jsonpath.out
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -1658,6 +1658,532 @@ select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ lik
  "a\b"
 (1 row)
 
+select jsonb_path_query('null', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() is applied to not a string
+select jsonb_path_query('true', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() is applied to not a string
+select jsonb_path_query('1', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() is applied to not a string
+select jsonb_path_query('[]', '$.datetime()');
+ jsonb_path_query 
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'strict $.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() is applied to not a string
+select jsonb_path_query('{}', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() is applied to not a string
+select jsonb_path_query('""', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  unrecognized datetime format
+HINT:  use datetime template argument for explicit format specification
+select jsonb_path_query('"12:34"', '$.datetime("aaa")');
+ERROR:  trailing characters remain in input string after datetime format
+select jsonb_path_query('"aaaa"', '$.datetime("HH24")');
+ERROR:  invalid value "aa" for "HH24"
+DETAIL:  Value must be an integer.
+select jsonb '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")';
+ ?column? 
+----------
+ t
+(1 row)
+
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+ jsonb_path_query 
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+ jsonb_path_query 
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+ERROR:  trailing characters remain in input string after datetime format
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+ERROR:  trailing characters remain in input string after datetime format
+select jsonb_path_query('"10-03-2017 12:34"', '       $.datetime("dd-mm-yyyy HH24:MI").type()');
+       jsonb_path_query        
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+      jsonb_path_query      
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+     jsonb_path_query     
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+   jsonb_path_query    
+-----------------------
+ "time with time zone"
+(1 row)
+
+set time zone '+00';
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+ jsonb_path_query 
+------------------
+ "12:34:00"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone '+10';
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+ jsonb_path_query 
+------------------
+ "12:34:00"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone default;
+select jsonb_path_query('"2017-03-10"', '$.datetime().type()');
+ jsonb_path_query 
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"2017-03-10"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+       jsonb_path_query        
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:34:56"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()');
+      jsonb_path_query      
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:56+03:00"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()');
+      jsonb_path_query      
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:56+03:10"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime().type()');
+     jsonb_path_query     
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "12:34:56"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()');
+   jsonb_path_query    
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "12:34:56+03:00"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()');
+   jsonb_path_query    
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "12:34:56+03:10"
+(1 row)
+
+set time zone '+00';
+-- date comparison
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR:  can't convert value from date to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR:  can't convert value from date to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR:  can't convert value from date to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10"
+ "2017-03-10T00:00:00"
+ "2017-03-10T03:00:00+03:00"
+(3 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10"
+ "2017-03-11"
+ "2017-03-10T00:00:00"
+ "2017-03-10T12:34:56"
+ "2017-03-10T03:00:00+03:00"
+(5 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-09"
+ "2017-03-10T01:02:03+04:00"
+(2 rows)
+
+-- time comparison
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+ERROR:  can't convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+ERROR:  can't convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+ERROR:  can't convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00"
+ "12:35:00+00:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00"
+ "12:36:00"
+ "12:35:00+00:00"
+(3 rows)
+
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:34:00"
+ "12:35:00+01:00"
+ "13:35:00+01:00"
+(3 rows)
+
+-- timetz comparison
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR:  can't convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR:  can't convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR:  can't convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00+01:00"
+(1 row)
+
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00+01:00"
+ "12:36:00+01:00"
+ "12:35:00-02:00"
+ "11:35:00"
+ "12:35:00"
+(5 rows)
+
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:34:00+01:00"
+ "12:35:00+02:00"
+ "10:35:00"
+(3 rows)
+
+-- timestamp comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR:  can't convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR:  can't convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR:  can't convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T13:35:00+01:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T12:36:00"
+ "2017-03-10T13:35:00+01:00"
+ "2017-03-10T12:35:00-01:00"
+ "2017-03-11"
+(5 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:34:00"
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10"
+(3 rows)
+
+-- timestamptz comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR:  can't convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR:  can't convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR:  can't convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T11:35:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+set time zone default;
 -- jsonpath operators
 SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
  jsonb_path_query 
diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
index ecdd453942b..f5d5fa4fb8d 100644
--- a/src/test/regress/expected/jsonpath.out
+++ b/src/test/regress/expected/jsonpath.out
@@ -401,6 +401,18 @@ select '$.keyvalue().key'::jsonpath;
  $.keyvalue()."key"
 (1 row)
 
+select '$.datetime()'::jsonpath;
+   jsonpath   
+--------------
+ $.datetime()
+(1 row)
+
+select '$.datetime("datetime template")'::jsonpath;
+            jsonpath             
+---------------------------------
+ $.datetime("datetime template")
+(1 row)
+
 select '$ ? (@ starts with "abc")'::jsonpath;
         jsonpath         
 -------------------------
diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
index 733fbd4e0d0..2c16182e554 100644
--- a/src/test/regress/sql/jsonb_jsonpath.sql
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -346,6 +346,178 @@ select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ lik
 select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\B$" flag "iq")');
 select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\b$" flag "")');
 
+select jsonb_path_query('null', '$.datetime()');
+select jsonb_path_query('true', '$.datetime()');
+select jsonb_path_query('1', '$.datetime()');
+select jsonb_path_query('[]', '$.datetime()');
+select jsonb_path_query('[]', 'strict $.datetime()');
+select jsonb_path_query('{}', '$.datetime()');
+select jsonb_path_query('""', '$.datetime()');
+select jsonb_path_query('"12:34"', '$.datetime("aaa")');
+select jsonb_path_query('"aaaa"', '$.datetime("HH24")');
+
+select jsonb '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")';
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+
+select jsonb_path_query('"10-03-2017 12:34"', '       $.datetime("dd-mm-yyyy HH24:MI").type()');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+
+set time zone '+00';
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone '+10';
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone default;
+
+select jsonb_path_query('"2017-03-10"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()');
+select jsonb_path_query('"12:34:56"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56"', '$.datetime()');
+select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56 +3"', '$.datetime()');
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()');
+
+set time zone '+00';
+
+-- date comparison
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+
+-- time comparison
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+
+-- timetz comparison
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+
+-- timestamp comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+
+-- timestamptz comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+
+set time zone default;
+
 -- jsonpath operators
 
 SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
index 29ea77a4858..713d32400a7 100644
--- a/src/test/regress/sql/jsonpath.sql
+++ b/src/test/regress/sql/jsonpath.sql
@@ -72,6 +72,8 @@ select '"aaa".type()'::jsonpath;
 select 'true.type()'::jsonpath;
 select '$.double().floor().ceiling().abs()'::jsonpath;
 select '$.keyvalue().key'::jsonpath;
+select '$.datetime()'::jsonpath;
+select '$.datetime("datetime template")'::jsonpath;
 
 select '$ ? (@ starts with "abc")'::jsonpath;
 select '$ ? (@ starts with $var)'::jsonpath;
#6Liudmila Mantrova
l.mantrova@postgrespro.ru
In reply to: Alexander Korotkov (#5)
4 attachment(s)
Re: Support for jsonpath .datetime() method

On 7/16/19 6:41 AM, Alexander Korotkov wrote:

Hi!

Thank you for the review!

Revised version of patch is attached.

On Mon, Jul 15, 2019 at 3:57 PM Anastasia Lubennikova
<lubennikovaav@gmail.com> wrote:

In general, the feature looks good. It is consistent with the standard and the code around.
It definitely needs more documentation - datetime() and new jsonb_path_*_tz() functions are not documented.

Documentation is added for both jsonpath .datetime() method and SQL
jsonb_path_*_tz() functions.

Here are also minor questions on implementation and code style:

1) + case jbvDatetime:
elog(ERROR, "unexpected jbvBinary value");
We should use separate error message for jvbDatetime here.

Fixed.

2) + *jentry = JENTRY_ISSTRING | len;
Here we can avoid using JENTRY_ISSTRING since it defined to 0x0.
I propose to do so to be consistent with jbvString case.

Fixed.

3)
+ * Default time-zone for tz types is specified with 'tzname'.  If 'tzname' is
+ * NULL and the input string does not contain zone components then "missing tz"
+ * error is thrown.
+ */
+Datum
+parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
+               int32 *typmod, int *tz)

The comment about 'tzname' is outdated.

Fixed.

4) Some typos:

+ * Convinience macros for error handling

* Convenience macros for error handling

+ * Two macros below helps handling errors in functions, which takes

* Two macros below help to handle errors in functions, which take

Fixed.

5) + * RETURN_ERROR() macro intended to wrap ereport() calls. When have_error
+ * argument is provided, then instead of ereport'ing we set *have_error flag

have_error is not a macro argument, so I suggest to rephrase this comment.

Shouldn't we pass have_error explicitly?
In case someone will change the name of the variable, this macro will work incorrectly.

Comment about RETURN_ERROR() is updated. RETURN_ERROR() is just
file-wide macro. So I think in this case it's ok to pass *have_error
flag implicitly for the sake of brevity.

6) * When no argument is supplied, first fitting ISO format is selected.
+        /* Try to recognize one of ISO formats. */
+        static const char *fmt_str[] =
+        {
+            "yyyy-mm-dd HH24:MI:SS TZH:TZM",
+            "yyyy-mm-dd HH24:MI:SS TZH",
+            "yyyy-mm-dd HH24:MI:SS",
+            "yyyy-mm-dd",
+            "HH24:MI:SS TZH:TZM",
+            "HH24:MI:SS TZH",
+            "HH24:MI:SS"
+        };

How do we choose the order of formats to check? Is it in standard?
Anyway, I think this struct needs a comment that explains that changing of order can affect end-user.

Yes, standard defines which order we should try datetime types (and
corresponding ISO formats). I've updated respectively array, its
comment and docs.

7) +            if (res == jperNotFound)
+                       RETURN_ERROR(ereport(ERROR,
+                                                                (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+                                                                 errmsg("invalid argument for SQL/JSON datetime function"),
+                                                                 errdetail("unrecognized datetime format"),
+                                                                 errhint("use datetime template argument for explicit format specification"))));

The hint is confusing. If I understand correctly, no-arg datetime function supports all formats,
so if parsing failed, it must be an invalid argument and providing format explicitly won't help.

Custom format string may define format not enumerated in fmt_str[].
For instance, imagine "dd.mm.yyyy". In some cases custom format
string can fix the error. So, ISTM hint is OK.

I'm setting this back to "Needs review" waiting for either you or
Peter Eisentraut provide additional review.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Hi Alexander,

I had look at the added docs and would like to suggest a couple of
changes. Please see the attached patches with my my edits for func.sgml
and some of the comments.

Looks like we also need to change the following entry in
features-unsupported.sgml, and probably move it to features-supported.sgml?

 <row>
  <entry>T832</entry>
  <entry></entry>
  <entry>SQL/JSON path language: item method</entry>
  <entry>datetime() not yet implemented</entry>
 </row>

--
Liudmila Mantrova
Technical writer at Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0004-implement-jsonpath-datetime-5.patchtext/x-patch; name=0004-implement-jsonpath-datetime-5.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index afde816..778a886 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -11746,16 +11746,6 @@ table2-mapping
   <itemizedlist>
    <listitem>
     <para>
-     <literal>.datetime()</literal> item method is not implemented yet
-     mainly because immutable <type>jsonpath</type> functions and operators
-     cannot reference session timezone, which is used in some datetime
-     operations.  Datetime support will be added to <type>jsonpath</type>
-     in future versions of <productname>PostgreSQL</productname>.
-    </para>
-   </listitem>
-
-   <listitem>
-    <para>
      A path expression can be a Boolean predicate, although the SQL/JSON
      standard allows predicates only in filters.  This is necessary for
      implementation of the <literal>@@</literal> operator. For example,
@@ -11960,6 +11950,20 @@ table2-mapping
         <entry><literal>0.3</literal></entry>
        </row>
        <row>
+        <entry><literal>datetime()</literal></entry>
+        <entry>Date/time value converted from a string</entry>
+        <entry><literal>["2015-8-1", "2015-08-12"]</literal></entry>
+        <entry><literal>$[*] ? (@.datetime() &lt; "2015-08-2". datetime())</literal></entry>
+        <entry><literal>2015-8-1</literal></entry>
+       </row>
+       <row>
+        <entry><literal>datetime(<replaceable>template</replaceable>)</literal></entry>
+        <entry>Date/time value converted from a string using the specified template</entry>
+        <entry><literal>["12:30", "18:40"]</literal></entry>
+        <entry><literal>$[*].datetime("HH24:MI")</literal></entry>
+        <entry><literal>"12:30:00", "18:40:00"</literal></entry>
+       </row>
+       <row>
         <entry><literal>keyvalue()</literal></entry>
         <entry>
           Sequence of object's key-value pairs represented as array of objects
@@ -11976,6 +11980,26 @@ table2-mapping
      </tgroup>
     </table>
 
+    <note>
+     <para>
+      The result type of <literal>datetime()</literal> and
+      <literal>datetime(<replaceable>template</replaceable>)</literal>
+      methods can be <type>date</type>, <type>timetz</type>, <type>time</type>,
+      <type>timestamptz</type>, or <type>timestamp</type>.
+      Both methods determine the result type dynamically.
+     </para>
+     <para>
+      The <literal>datetime()</literal> method sequentially tries ISO formats
+      for <type>date</type>, <type>timetz</type>, <type>time</type>,
+      <type>timestamptz</type>, and <type>timestamp</type>. It stops on
+      the first matching format and the corresponding data type.
+     </para>
+     <para>
+      The <literal>datetime(<replaceable>template</replaceable>)</literal>
+      method determines the result type by the provided template string.
+     </para>
+    </note>
+
     <table id="functions-sqljson-filter-ex-table">
      <title><type>jsonpath</type> Filter Expression Elements</title>
      <tgroup cols="5">
@@ -12118,6 +12142,15 @@ table2-mapping
       </tbody>
      </tgroup>
     </table>
+
+    <note>
+     <para>
+      When different date/time values are compared, an implicit cast is
+      applied. A <type>date</type> value can be cast to <type>timestamp</type>
+      or <type>timestamptz</type>, <type>timestamp</type> can be cast to
+      <type>timestamptz</type>, and <type>time</type> &mdash; to <type>timetz</type>.
+     </para>
+    </note>
    </sect3>
 
   </sect2>
@@ -12351,7 +12384,7 @@ table2-mapping
    <para>
     The <literal>@?</literal> and <literal>@@</literal> operators suppress
     the following errors: lacking object field or array element, unexpected
-    JSON item type, and numeric errors.
+    JSON item type, datetime and numeric errors.
     This behavior might be helpful while searching over JSON document
     collections of varying structure.
    </para>
@@ -12621,17 +12654,32 @@ table2-mapping
    <primary>jsonb_path_exists</primary>
   </indexterm>
   <indexterm>
+   <primary>jsonb_path_exists_tz</primary>
+  </indexterm>
+  <indexterm>
    <primary>jsonb_path_match</primary>
   </indexterm>
   <indexterm>
+   <primary>jsonb_path_match_tz</primary>
+  </indexterm>
+  <indexterm>
    <primary>jsonb_path_query</primary>
   </indexterm>
   <indexterm>
+   <primary>jsonb_path_query_tz</primary>
+  </indexterm>
+  <indexterm>
    <primary>jsonb_path_query_array</primary>
   </indexterm>
   <indexterm>
+   <primary>jsonb_path_query_array_tz</primary>
+  </indexterm>
+  <indexterm>
    <primary>jsonb_path_query_first</primary>
   </indexterm>
+  <indexterm>
+   <primary>jsonb_path_query_first_tz</primary>
+  </indexterm>
 
   <table id="functions-json-processing-table">
     <title>JSON Processing Functions</title>
@@ -12971,6 +13019,9 @@ table2-mapping
          <para><literal>
            jsonb_path_exists(target jsonb, path jsonpath [, vars jsonb, silent bool])
          </literal></para>
+         <para><literal>
+           jsonb_path_exists_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>boolean</type></entry>
         <entry>
@@ -12991,6 +13042,9 @@ table2-mapping
          <para><literal>
            jsonb_path_match(target jsonb, path jsonpath [, vars jsonb, silent bool])
          </literal></para>
+         <para><literal>
+           jsonb_path_match_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>boolean</type></entry>
         <entry>
@@ -13013,6 +13067,9 @@ table2-mapping
          <para><literal>
            jsonb_path_query(target jsonb, path jsonpath [, vars jsonb, silent bool])
          </literal></para>
+         <para><literal>
+           jsonb_path_query_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>setof jsonb</type></entry>
         <entry>
@@ -13041,6 +13098,9 @@ table2-mapping
          <para><literal>
            jsonb_path_query_array(target jsonb, path jsonpath [, vars jsonb, silent bool])
          </literal></para>
+         <para><literal>
+           jsonb_path_query_array_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>jsonb</type></entry>
         <entry>
@@ -13061,6 +13121,9 @@ table2-mapping
          <para><literal>
            jsonb_path_query_first(target jsonb, path jsonpath [, vars jsonb, silent bool])
          </literal></para>
+         <para><literal>
+           jsonb_path_query_first_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>jsonb</type></entry>
         <entry>
@@ -13203,11 +13266,8 @@ table2-mapping
 
   <note>
    <para>
-    The <literal>jsonb_path_exists</literal>, <literal>jsonb_path_match</literal>,
-    <literal>jsonb_path_query</literal>, <literal>jsonb_path_query_array</literal> and
-    <literal>jsonb_path_query_first</literal>
-    functions have optional <literal>vars</literal> and <literal>silent</literal>
-    arguments.
+    The <literal>jsonb_path_*</literal> functions have optional
+    <literal>vars</literal> and <literal>silent</literal> arguments.
    </para>
    <para>
     If the <literal>vars</literal> argument is specified, it provides an
@@ -13221,6 +13281,20 @@ table2-mapping
    </para>
   </note>
 
+  <note>
+   <para>
+    Some of the <literal>jsonb_path_*</literal> functions have the
+    <literal>_tz</literal> suffix. These functions have been implemented to
+    support comparison of date/time values that involves implicit
+    timezone-aware casts. Since operations with time zones are not immutable,
+    these functions are qualified as stable. Their counterparts without the
+    suffix do not support such casts, so they are immutable and can be used for
+    such use-cases as expression indexes
+    (see <xref linkend="indexes-expressional"/>). There is no difference
+    between these functions for other <type>jsonpath</type> operations.
+   </para>
+  </note>
+
   <para>
     See also <xref linkend="functions-aggregate"/> for the aggregate
     function <function>json_agg</function> which aggregates record
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ea4c85e..423ae4f 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1287,6 +1287,46 @@ LANGUAGE INTERNAL
 STRICT IMMUTABLE PARALLEL SAFE
 AS 'jsonb_path_query_first';
 
+CREATE OR REPLACE FUNCTION
+  jsonb_path_exists_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                    silent boolean DEFAULT false)
+RETURNS boolean
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_exists_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_match_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                   silent boolean DEFAULT false)
+RETURNS boolean
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_match_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_query_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                   silent boolean DEFAULT false)
+RETURNS SETOF jsonb
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_query_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_query_array_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                         silent boolean DEFAULT false)
+RETURNS jsonb
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_query_array_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_query_first_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                         silent boolean DEFAULT false)
+RETURNS jsonb
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_query_first_tz';
+
 --
 -- The default permissions for functions mean that anyone can execute them.
 -- A number of functions shouldn't be executable by just anyone, but rather
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 87ae60e..65c047a 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -337,12 +337,14 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
 		case jpiPlus:
 		case jpiMinus:
 		case jpiExists:
+		case jpiDatetime:
 			{
 				int32		arg = reserveSpaceForItemPointer(buf);
 
-				chld = flattenJsonPathParseItem(buf, item->value.arg,
-												nestingLevel + argNestingLevel,
-												insideArraySubscript);
+				chld = !item->value.arg ? pos :
+					flattenJsonPathParseItem(buf, item->value.arg,
+											 nestingLevel + argNestingLevel,
+											 insideArraySubscript);
 				*(int32 *) (buf->data + arg) = chld - pos;
 			}
 			break;
@@ -692,6 +694,15 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
 		case jpiDouble:
 			appendBinaryStringInfo(buf, ".double()", 9);
 			break;
+		case jpiDatetime:
+			appendBinaryStringInfo(buf, ".datetime(", 10);
+			if (v->content.arg)
+			{
+				jspGetArg(v, &elem);
+				printJsonPathItem(buf, &elem, false, false);
+			}
+			appendStringInfoChar(buf, ')');
+			break;
 		case jpiKeyValue:
 			appendBinaryStringInfo(buf, ".keyvalue()", 11);
 			break;
@@ -754,6 +765,8 @@ jspOperationName(JsonPathItemType type)
 			return "floor";
 		case jpiCeiling:
 			return "ceiling";
+		case jpiDatetime:
+			return "datetime";
 		default:
 			elog(ERROR, "unrecognized jsonpath item type: %d", type);
 			return NULL;
@@ -889,6 +902,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
 		case jpiPlus:
 		case jpiMinus:
 		case jpiFilter:
+		case jpiDatetime:
 			read_int32(v->content.arg, base, pos);
 			break;
 		case jpiIndexArray:
@@ -913,7 +927,8 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a)
 		   v->type == jpiIsUnknown ||
 		   v->type == jpiExists ||
 		   v->type == jpiPlus ||
-		   v->type == jpiMinus);
+		   v->type == jpiMinus ||
+		   v->type == jpiDatetime);
 
 	jspInitByBuffer(a, v->base, v->content.arg);
 }
@@ -961,6 +976,7 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
 			   v->type == jpiFloor ||
 			   v->type == jpiCeiling ||
 			   v->type == jpiDouble ||
+			   v->type == jpiDatetime ||
 			   v->type == jpiKeyValue ||
 			   v->type == jpiStartsWith);
 
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index d8647f7..6284da2 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -66,6 +66,7 @@
 #include "miscadmin.h"
 #include "regex/regex.h"
 #include "utils/builtins.h"
+#include "utils/datetime.h"
 #include "utils/datum.h"
 #include "utils/formatting.h"
 #include "utils/float.h"
@@ -107,6 +108,7 @@ typedef struct JsonPathExecContext
 										 * ignored */
 	bool		throwErrors;	/* with "false" all suppressible errors are
 								 * suppressed */
+	bool		useTz;
 } JsonPathExecContext;
 
 /* Context for LIKE_REGEX execution. */
@@ -173,7 +175,8 @@ typedef JsonPathBool (*JsonPathPredicateCallback) (JsonPathItem *jsp,
 typedef Numeric (*BinaryArithmFunc) (Numeric num1, Numeric num2, bool *error);
 
 static JsonPathExecResult executeJsonPath(JsonPath *path, Jsonb *vars,
-										  Jsonb *json, bool throwErrors, JsonValueList *result);
+										  Jsonb *json, bool throwErrors,
+										  JsonValueList *result, bool useTz);
 static JsonPathExecResult executeItem(JsonPathExecContext *cxt,
 									  JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
 static JsonPathExecResult executeItemOptUnwrapTarget(JsonPathExecContext *cxt,
@@ -216,6 +219,8 @@ static JsonPathBool executeLikeRegex(JsonPathItem *jsp, JsonbValue *str,
 static JsonPathExecResult executeNumericItemMethod(JsonPathExecContext *cxt,
 												   JsonPathItem *jsp, JsonbValue *jb, bool unwrap, PGFunction func,
 												   JsonValueList *found);
+static JsonPathExecResult executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
+												JsonbValue *jb, JsonValueList *found);
 static JsonPathExecResult executeKeyValueMethod(JsonPathExecContext *cxt,
 												JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
 static JsonPathExecResult appendBoolResult(JsonPathExecContext *cxt,
@@ -227,7 +232,8 @@ static void getJsonPathVariable(JsonPathExecContext *cxt,
 static int	JsonbArraySize(JsonbValue *jb);
 static JsonPathBool executeComparison(JsonPathItem *cmp, JsonbValue *lv,
 									  JsonbValue *rv, void *p);
-static JsonPathBool compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2);
+static JsonPathBool compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2,
+								 bool useTz);
 static int	compareNumeric(Numeric a, Numeric b);
 static JsonbValue *copyJsonbValue(JsonbValue *src);
 static JsonPathExecResult getArrayIndex(JsonPathExecContext *cxt,
@@ -248,6 +254,8 @@ static JsonbValue *JsonbInitBinary(JsonbValue *jbv, Jsonb *jb);
 static int	JsonbType(JsonbValue *jb);
 static JsonbValue *getScalar(JsonbValue *scalar, enum jbvType type);
 static JsonbValue *wrapItemsInArray(const JsonValueList *items);
+static int	compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
+							bool useTz, bool *have_error);
 
 /****************** User interface to JsonPath executor ********************/
 
@@ -263,8 +271,8 @@ static JsonbValue *wrapItemsInArray(const JsonValueList *items);
  *		SQL/JSON.  Regarding jsonb_path_match(), this function doesn't have
  *		an analogy in SQL/JSON, so we define its behavior on our own.
  */
-Datum
-jsonb_path_exists(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_exists_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -278,7 +286,7 @@ jsonb_path_exists(PG_FUNCTION_ARGS)
 		silent = PG_GETARG_BOOL(3);
 	}
 
-	res = executeJsonPath(jp, vars, jb, !silent, NULL);
+	res = executeJsonPath(jp, vars, jb, !silent, NULL, tz);
 
 	PG_FREE_IF_COPY(jb, 0);
 	PG_FREE_IF_COPY(jp, 1);
@@ -289,6 +297,18 @@ jsonb_path_exists(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(res == jperOk);
 }
 
+Datum
+jsonb_path_exists(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_exists_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_exists_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_exists_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_exists_opr
  *		Implementation of operator "jsonb @? jsonpath" (2-argument version of
@@ -298,7 +318,7 @@ Datum
 jsonb_path_exists_opr(PG_FUNCTION_ARGS)
 {
 	/* just call the other one -- it can handle both cases */
-	return jsonb_path_exists(fcinfo);
+	return jsonb_path_exists_internal(fcinfo, false);
 }
 
 /*
@@ -306,8 +326,8 @@ jsonb_path_exists_opr(PG_FUNCTION_ARGS)
  *		Returns jsonpath predicate result item for the specified jsonb value.
  *		See jsonb_path_exists() comment for details regarding error handling.
  */
-Datum
-jsonb_path_match(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_match_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -321,7 +341,7 @@ jsonb_path_match(PG_FUNCTION_ARGS)
 		silent = PG_GETARG_BOOL(3);
 	}
 
-	(void) executeJsonPath(jp, vars, jb, !silent, &found);
+	(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 	PG_FREE_IF_COPY(jb, 0);
 	PG_FREE_IF_COPY(jp, 1);
@@ -345,6 +365,18 @@ jsonb_path_match(PG_FUNCTION_ARGS)
 	PG_RETURN_NULL();
 }
 
+Datum
+jsonb_path_match(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_match_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_match_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_match_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_match_opr
  *		Implementation of operator "jsonb @@ jsonpath" (2-argument version of
@@ -354,7 +386,7 @@ Datum
 jsonb_path_match_opr(PG_FUNCTION_ARGS)
 {
 	/* just call the other one -- it can handle both cases */
-	return jsonb_path_match(fcinfo);
+	return jsonb_path_match_internal(fcinfo, false);
 }
 
 /*
@@ -362,8 +394,8 @@ jsonb_path_match_opr(PG_FUNCTION_ARGS)
  *		Executes jsonpath for given jsonb document and returns result as
  *		rowset.
  */
-Datum
-jsonb_path_query(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_query_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	FuncCallContext *funcctx;
 	List	   *found;
@@ -387,7 +419,7 @@ jsonb_path_query(PG_FUNCTION_ARGS)
 		vars = PG_GETARG_JSONB_P_COPY(2);
 		silent = PG_GETARG_BOOL(3);
 
-		(void) executeJsonPath(jp, vars, jb, !silent, &found);
+		(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 		funcctx->user_fctx = JsonValueListGetList(&found);
 
@@ -408,13 +440,25 @@ jsonb_path_query(PG_FUNCTION_ARGS)
 	SRF_RETURN_NEXT(funcctx, JsonbPGetDatum(JsonbValueToJsonb(v)));
 }
 
+Datum
+jsonb_path_query(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_query_array
  *		Executes jsonpath for given jsonb document and returns result as
  *		jsonb array.
  */
-Datum
-jsonb_path_query_array(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_query_array_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -422,18 +466,30 @@ jsonb_path_query_array(PG_FUNCTION_ARGS)
 	Jsonb	   *vars = PG_GETARG_JSONB_P(2);
 	bool		silent = PG_GETARG_BOOL(3);
 
-	(void) executeJsonPath(jp, vars, jb, !silent, &found);
+	(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 	PG_RETURN_JSONB_P(JsonbValueToJsonb(wrapItemsInArray(&found)));
 }
 
+Datum
+jsonb_path_query_array(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_array_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_array_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_array_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_query_first
  *		Executes jsonpath for given jsonb document and returns first result
  *		item.  If there are no items, NULL returned.
  */
-Datum
-jsonb_path_query_first(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_query_first_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -441,7 +497,7 @@ jsonb_path_query_first(PG_FUNCTION_ARGS)
 	Jsonb	   *vars = PG_GETARG_JSONB_P(2);
 	bool		silent = PG_GETARG_BOOL(3);
 
-	(void) executeJsonPath(jp, vars, jb, !silent, &found);
+	(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 	if (JsonValueListLength(&found) >= 1)
 		PG_RETURN_JSONB_P(JsonbValueToJsonb(JsonValueListHead(&found)));
@@ -449,6 +505,18 @@ jsonb_path_query_first(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 }
 
+Datum
+jsonb_path_query_first(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_first_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_first_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_first_internal(fcinfo, true);
+}
+
 /********************Execute functions for JsonPath**************************/
 
 /*
@@ -472,7 +540,7 @@ jsonb_path_query_first(PG_FUNCTION_ARGS)
  */
 static JsonPathExecResult
 executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors,
-				JsonValueList *result)
+				JsonValueList *result, bool useTz)
 {
 	JsonPathExecContext cxt;
 	JsonPathExecResult res;
@@ -502,6 +570,7 @@ executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors,
 	cxt.lastGeneratedObjectId = vars ? 2 : 1;
 	cxt.innermostArraySize = -1;
 	cxt.throwErrors = throwErrors;
+	cxt.useTz = useTz;
 
 	if (jspStrictAbsenseOfErrors(&cxt) && !result)
 	{
@@ -1030,6 +1099,12 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
 			}
 			break;
 
+		case jpiDatetime:
+			if (unwrap && JsonbType(jb) == jbvArray)
+				return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
+
+			return executeDateTimeMethod(cxt, jsp, jb, found);
+
 		case jpiKeyValue:
 			if (unwrap && JsonbType(jb) == jbvArray)
 				return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
@@ -1216,7 +1291,7 @@ executeBoolItem(JsonPathExecContext *cxt, JsonPathItem *jsp,
 			jspGetLeftArg(jsp, &larg);
 			jspGetRightArg(jsp, &rarg);
 			return executePredicate(cxt, jsp, &larg, &rarg, jb, true,
-									executeComparison, NULL);
+									executeComparison, cxt);
 
 		case jpiStartsWith:		/* 'whole STARTS WITH initial' */
 			jspGetLeftArg(jsp, &larg);	/* 'whole' */
@@ -1720,6 +1795,138 @@ executeNumericItemMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
 }
 
 /*
+ * Implementation of the .datetime() method.
+ *
+ * Converts a string into a date/time value. The actual type is determined at run time.
+ * If an argument is provided, this argument is used as a template string.
+ * Otherwise, the first fitting ISO format is selected.
+ */
+static JsonPathExecResult
+executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
+					  JsonbValue *jb, JsonValueList *found)
+{
+	JsonbValue	jbvbuf;
+	Datum		value;
+	text	   *datetime;
+	Oid			typid;
+	int32		typmod = -1;
+	int			tz = 0;
+	bool		hasNext;
+	JsonPathExecResult res = jperNotFound;
+	JsonPathItem elem;
+
+	if (!(jb = getScalar(jb, jbvString)))
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+							  errmsg("invalid argument for SQL/JSON datetime function"),
+							  errdetail("jsonpath item method .%s() can only be applied to a string",
+										jspOperationName(jsp->type)))));
+
+	datetime = cstring_to_text_with_len(jb->val.string.val,
+										jb->val.string.len);
+
+	if (jsp->content.arg)
+	{
+		text	   *template;
+		char	   *template_str;
+		int			template_len;
+		bool		have_error = false;
+
+		jspGetArg(jsp, &elem);
+
+		if (elem.type != jpiString)
+			elog(ERROR, "invalid jsonpath item type for .datetime() argument");
+
+		template_str = jspGetString(&elem, &template_len);
+
+		template = cstring_to_text_with_len(template_str,
+											template_len);
+
+		value = parse_datetime(datetime, template, true,
+							   &typid, &typmod, &tz,
+							   jspThrowErrors(cxt) ? NULL : &have_error);
+
+		if (have_error)
+			res = jperError;
+		else
+			res = jperOk;
+	}
+	else
+	{
+		/*
+		 * According to SQL/JSON standard enumerate ISO formats for: date,
+		 * timetz, time, timestamptz, timestamp.
+		 */
+		static const char *fmt_str[] =
+		{
+			"yyyy-mm-dd",
+			"HH24:MI:SS TZH:TZM",
+			"HH24:MI:SS TZH",
+			"HH24:MI:SS",
+			"yyyy-mm-dd HH24:MI:SS TZH:TZM",
+			"yyyy-mm-dd HH24:MI:SS TZH",
+			"yyyy-mm-dd HH24:MI:SS"
+		};
+
+		/* cache for format texts */
+		static text *fmt_txt[lengthof(fmt_str)] = {0};
+		int			i;
+
+		/* loop until datetime format fits */
+		for (i = 0; i < lengthof(fmt_str); i++)
+		{
+			bool		have_error = false;
+
+			if (!fmt_txt[i])
+			{
+				MemoryContext oldcxt =
+				MemoryContextSwitchTo(TopMemoryContext);
+
+				fmt_txt[i] = cstring_to_text(fmt_str[i]);
+				MemoryContextSwitchTo(oldcxt);
+			}
+
+			value = parse_datetime(datetime, fmt_txt[i], true,
+								   &typid, &typmod, &tz,
+								   &have_error);
+
+			if (!have_error)
+			{
+				res = jperOk;
+				break;
+			}
+		}
+
+		if (res == jperNotFound)
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+								  errmsg("invalid argument for SQL/JSON datetime function"),
+								  errdetail("unrecognized datetime format"),
+								  errhint("use datetime template argument for explicit format specification"))));
+	}
+
+	pfree(datetime);
+
+	if (jperIsError(res))
+		return res;
+
+	hasNext = jspGetNext(jsp, &elem);
+
+	if (!hasNext && !found)
+		return res;
+
+	jb = hasNext ? &jbvbuf : palloc(sizeof(*jb));
+
+	jb->type = jbvDatetime;
+	jb->val.datetime.value = value;
+	jb->val.datetime.typid = typid;
+	jb->val.datetime.typmod = typmod;
+	jb->val.datetime.tz = tz;
+
+	return executeNextItem(cxt, jsp, &elem, jb, found, hasNext);
+}
+
+/*
  * Implementation of .keyvalue() method.
  *
  * .keyvalue() method returns a sequence of object's key-value pairs in the
@@ -1979,14 +2186,16 @@ JsonbArraySize(JsonbValue *jb)
 static JsonPathBool
 executeComparison(JsonPathItem *cmp, JsonbValue *lv, JsonbValue *rv, void *p)
 {
-	return compareItems(cmp->type, lv, rv);
+	JsonPathExecContext *cxt = (JsonPathExecContext *) p;
+
+	return compareItems(cmp->type, lv, rv, cxt->useTz);
 }
 
 /*
  * Compare two SQL/JSON items using comparison operation 'op'.
  */
 static JsonPathBool
-compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2)
+compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2, bool useTz)
 {
 	int			cmp;
 	bool		res;
@@ -2028,6 +2237,21 @@ compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2)
 							 jb2->val.string.val, jb2->val.string.len,
 							 DEFAULT_COLLATION_OID);
 			break;
+		case jbvDatetime:
+			{
+				bool		have_error = false;
+
+				cmp = compareDatetime(jb1->val.datetime.value,
+									  jb1->val.datetime.typid,
+									  jb2->val.datetime.value,
+									  jb2->val.datetime.typid,
+									  useTz,
+									  &have_error);
+
+				if (have_error)
+					return jpbUnknown;
+			}
+			break;
 
 		case jbvBinary:
 		case jbvArray:
@@ -2287,3 +2511,205 @@ wrapItemsInArray(const JsonValueList *items)
 
 	return pushJsonbValue(&ps, WJB_END_ARRAY, NULL);
 }
+
+/*
+ * Cross-type comparison of two datetime SQL/JSON items.  If items are
+ * uncomparable, 'error' flag is set.
+ */
+static int
+compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
+				bool useTz, bool *have_error)
+{
+	PGFunction cmpfunc = NULL;
+
+	switch (typid1)
+	{
+		case DATEOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					cmpfunc = date_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					val1 = date2timestamp_opt_error(val1, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"date", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val1 = date2timestamptz_opt_error(val1, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMEOID:
+			switch (typid2)
+			{
+				case TIMEOID:
+					cmpfunc = time_cmp;
+
+					break;
+
+				case TIMETZOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"time", "timetz"),
+								 errhint("use *_tz() function for timezone support")));
+					val1 = DirectFunctionCall1(time_timetz, val1);
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case DATEOID:
+				case TIMESTAMPOID:
+				case TIMESTAMPTZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMETZOID:
+			switch (typid2)
+			{
+				case TIMEOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"time", "timetz"),
+								 errhint("use *_tz() function for timezone support")));
+					val2 = DirectFunctionCall1(time_timetz, val2);
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case TIMETZOID:
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case DATEOID:
+				case TIMESTAMPOID:
+				case TIMESTAMPTZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMESTAMPOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					val2 = date2timestamp_opt_error(val2, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"timestamp", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val1 = timestamp2timestamptz_opt_error(val1, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMESTAMPTZOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"date", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val2 = date2timestamptz_opt_error(val2, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"timestamp", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val2 = timestamp2timestamptz_opt_error(val2, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		default:
+			elog(ERROR, "unrecognized SQL/JSON datetime type oid: %d",
+				 typid1);
+	}
+
+	if (*have_error)
+		return 0;
+
+	if (!cmpfunc)
+		elog(ERROR, "unrecognized SQL/JSON datetime type oid: %d",
+			 typid2);
+
+	*have_error = false;
+
+	return DatumGetInt32(DirectFunctionCall2(cmpfunc, val1, val2));
+}
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
index 91b4b2f..059faee 100644
--- a/src/backend/utils/adt/jsonpath_gram.y
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -94,12 +94,14 @@ static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr,
 %token	<str>		LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
 %token	<str>		ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P
 %token	<str>		ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P KEYVALUE_P
+%token	<str>		DATETIME_P
 
 %type	<result>	result
 
 %type	<value>		scalar_value path_primary expr array_accessor
 					any_path accessor_op key predicate delimited_predicate
 					index_elem starts_with_initial expr_or_predicate
+					datetime_template opt_datetime_template
 
 %type	<elems>		accessor_expr
 
@@ -247,9 +249,20 @@ accessor_op:
 	| array_accessor				{ $$ = $1; }
 	| '.' any_path					{ $$ = $2; }
 	| '.' method '(' ')'			{ $$ = makeItemType($2); }
+	| '.' DATETIME_P '(' opt_datetime_template ')'
+									{ $$ = makeItemUnary(jpiDatetime, $4); }
 	| '?' '(' predicate ')'			{ $$ = makeItemUnary(jpiFilter, $3); }
 	;
 
+datetime_template:
+	STRING_P						{ $$ = makeItemString(&$1); }
+	;
+
+opt_datetime_template:
+	datetime_template				{ $$ = $1; }
+	| /* EMPTY */					{ $$ = NULL; }
+	;
+
 key:
 	key_name						{ $$ = makeItemKey(&$1); }
 	;
@@ -272,6 +285,7 @@ key_name:
 	| FLOOR_P
 	| DOUBLE_P
 	| CEILING_P
+	| DATETIME_P
 	| KEYVALUE_P
 	| LAST_P
 	| STARTS_P
diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l
index 2165ffc..ced65ed 100644
--- a/src/backend/utils/adt/jsonpath_scan.l
+++ b/src/backend/utils/adt/jsonpath_scan.l
@@ -340,6 +340,7 @@ static const JsonPathKeyword keywords[] = {
 	{ 6, false,	STRICT_P,	"strict"},
 	{ 7, false,	CEILING_P,	"ceiling"},
 	{ 7, false,	UNKNOWN_P,	"unknown"},
+	{ 8, false,	DATETIME_P,	"datetime"},
 	{ 8, false,	KEYVALUE_P,	"keyvalue"},
 	{ 10,false, LIKE_REGEX_P, "like_regex"},
 };
diff --git a/src/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index 16f5ca2..7287653 100644
--- a/src/backend/utils/errcodes.txt
+++ b/src/backend/utils/errcodes.txt
@@ -207,6 +207,7 @@ Section: Class 22 - Data Exception
 2200S    E    ERRCODE_INVALID_XML_COMMENT                                    invalid_xml_comment
 2200T    E    ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION                     invalid_xml_processing_instruction
 22030    E    ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE                        duplicate_json_object_key_value
+22031    E    ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION            invalid_argument_for_json_datetime_function
 22032    E    ERRCODE_INVALID_JSON_TEXT                                      invalid_json_text
 22033    E    ERRCODE_INVALID_JSON_SUBSCRIPT                                 invalid_json_subscript
 22034    E    ERRCODE_MORE_THAN_ONE_JSON_ITEM                                more_than_one_json_item
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0902dce..4acdd7a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9323,6 +9323,28 @@
   proname => 'jsonb_path_match', prorettype => 'bool',
   proargtypes => 'jsonb jsonpath jsonb bool', prosrc => 'jsonb_path_match' },
 
+{ oid => '6015', descr => 'jsonpath exists test with timezone',
+  proname => 'jsonb_path_exists_tz', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_exists_tz' },
+{ oid => '6016', descr => 'jsonpath query with timezone',
+  proname => 'jsonb_path_query_tz', provolatile => 's',
+  prorows => '1000', proretset => 't',
+  prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_query_tz' },
+{ oid => '6017', descr => 'jsonpath query wrapped into array with timezone',
+  proname => 'jsonb_path_query_array_tz', provolatile => 's',
+  prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_query_array_tz' },
+{ oid => '6018', descr => 'jsonpath query first item with timezone',
+  proname => 'jsonb_path_query_first_tz', provolatile => 's',
+  prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_query_first_tz' },
+{ oid => '6019', descr => 'jsonpath match with timezone',
+  proname => 'jsonb_path_match_tz', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_match_tz' },
+
 { oid => '4010', descr => 'implementation of @? operator',
   proname => 'jsonb_path_exists_opr', prorettype => 'bool',
   proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_path_exists_opr' },
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 40ad5fd..f6b17c8 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -79,6 +79,7 @@ typedef enum JsonPathItemType
 	jpiFloor,					/* .floor() item method */
 	jpiCeiling,					/* .ceiling() item method */
 	jpiDouble,					/* .double() item method */
+	jpiDatetime,				/* .datetime() item method */
 	jpiKeyValue,				/* .keyvalue() item method */
 	jpiSubscript,				/* array subscript: 'expr' or 'expr TO expr' */
 	jpiLast,					/* LAST array subscript */
diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
index 31a871a..ac668a6 100644
--- a/src/test/regress/expected/jsonb_jsonpath.out
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -1658,6 +1658,532 @@ select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ lik
  "a\b"
 (1 row)
 
+select jsonb_path_query('null', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('true', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('1', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('[]', '$.datetime()');
+ jsonb_path_query 
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'strict $.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('{}', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('""', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  unrecognized datetime format
+HINT:  use datetime template argument for explicit format specification
+select jsonb_path_query('"12:34"', '$.datetime("aaa")');
+ERROR:  trailing characters remain in input string after datetime format
+select jsonb_path_query('"aaaa"', '$.datetime("HH24")');
+ERROR:  invalid value "aa" for "HH24"
+DETAIL:  Value must be an integer.
+select jsonb '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")';
+ ?column? 
+----------
+ t
+(1 row)
+
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+ jsonb_path_query 
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+ jsonb_path_query 
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+ERROR:  trailing characters remain in input string after datetime format
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+ERROR:  trailing characters remain in input string after datetime format
+select jsonb_path_query('"10-03-2017 12:34"', '       $.datetime("dd-mm-yyyy HH24:MI").type()');
+       jsonb_path_query        
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+      jsonb_path_query      
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+     jsonb_path_query     
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+   jsonb_path_query    
+-----------------------
+ "time with time zone"
+(1 row)
+
+set time zone '+00';
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+ jsonb_path_query 
+------------------
+ "12:34:00"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone '+10';
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+ jsonb_path_query 
+------------------
+ "12:34:00"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone default;
+select jsonb_path_query('"2017-03-10"', '$.datetime().type()');
+ jsonb_path_query 
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"2017-03-10"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+       jsonb_path_query        
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:34:56"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()');
+      jsonb_path_query      
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:56+03:00"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()');
+      jsonb_path_query      
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:56+03:10"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime().type()');
+     jsonb_path_query     
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "12:34:56"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()');
+   jsonb_path_query    
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "12:34:56+03:00"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()');
+   jsonb_path_query    
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "12:34:56+03:10"
+(1 row)
+
+set time zone '+00';
+-- date comparison
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR:  cannot convert value from date to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR:  cannot convert value from date to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR:  cannot convert value from date to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10"
+ "2017-03-10T00:00:00"
+ "2017-03-10T03:00:00+03:00"
+(3 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10"
+ "2017-03-11"
+ "2017-03-10T00:00:00"
+ "2017-03-10T12:34:56"
+ "2017-03-10T03:00:00+03:00"
+(5 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-09"
+ "2017-03-10T01:02:03+04:00"
+(2 rows)
+
+-- time comparison
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00"
+ "12:35:00+00:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00"
+ "12:36:00"
+ "12:35:00+00:00"
+(3 rows)
+
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:34:00"
+ "12:35:00+01:00"
+ "13:35:00+01:00"
+(3 rows)
+
+-- timetz comparison
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00+01:00"
+(1 row)
+
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00+01:00"
+ "12:36:00+01:00"
+ "12:35:00-02:00"
+ "11:35:00"
+ "12:35:00"
+(5 rows)
+
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:34:00+01:00"
+ "12:35:00+02:00"
+ "10:35:00"
+(3 rows)
+
+-- timestamp comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T13:35:00+01:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T12:36:00"
+ "2017-03-10T13:35:00+01:00"
+ "2017-03-10T12:35:00-01:00"
+ "2017-03-11"
+(5 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:34:00"
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10"
+(3 rows)
+
+-- timestamptz comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T11:35:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+set time zone default;
 -- jsonpath operators
 SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
  jsonb_path_query 
diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
index ecdd453..f5d5fa4 100644
--- a/src/test/regress/expected/jsonpath.out
+++ b/src/test/regress/expected/jsonpath.out
@@ -401,6 +401,18 @@ select '$.keyvalue().key'::jsonpath;
  $.keyvalue()."key"
 (1 row)
 
+select '$.datetime()'::jsonpath;
+   jsonpath   
+--------------
+ $.datetime()
+(1 row)
+
+select '$.datetime("datetime template")'::jsonpath;
+            jsonpath             
+---------------------------------
+ $.datetime("datetime template")
+(1 row)
+
 select '$ ? (@ starts with "abc")'::jsonpath;
         jsonpath         
 -------------------------
diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
index 733fbd4..2c16182 100644
--- a/src/test/regress/sql/jsonb_jsonpath.sql
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -346,6 +346,178 @@ select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ lik
 select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\B$" flag "iq")');
 select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\b$" flag "")');
 
+select jsonb_path_query('null', '$.datetime()');
+select jsonb_path_query('true', '$.datetime()');
+select jsonb_path_query('1', '$.datetime()');
+select jsonb_path_query('[]', '$.datetime()');
+select jsonb_path_query('[]', 'strict $.datetime()');
+select jsonb_path_query('{}', '$.datetime()');
+select jsonb_path_query('""', '$.datetime()');
+select jsonb_path_query('"12:34"', '$.datetime("aaa")');
+select jsonb_path_query('"aaaa"', '$.datetime("HH24")');
+
+select jsonb '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")';
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+
+select jsonb_path_query('"10-03-2017 12:34"', '       $.datetime("dd-mm-yyyy HH24:MI").type()');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+
+set time zone '+00';
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone '+10';
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone default;
+
+select jsonb_path_query('"2017-03-10"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()');
+select jsonb_path_query('"12:34:56"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56"', '$.datetime()');
+select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56 +3"', '$.datetime()');
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()');
+
+set time zone '+00';
+
+-- date comparison
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+
+-- time comparison
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+
+-- timetz comparison
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+
+-- timestamp comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+
+-- timestamptz comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+
+set time zone default;
+
 -- jsonpath operators
 
 SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
index 29ea77a..713d324 100644
--- a/src/test/regress/sql/jsonpath.sql
+++ b/src/test/regress/sql/jsonpath.sql
@@ -72,6 +72,8 @@ select '"aaa".type()'::jsonpath;
 select 'true.type()'::jsonpath;
 select '$.double().floor().ceiling().abs()'::jsonpath;
 select '$.keyvalue().key'::jsonpath;
+select '$.datetime()'::jsonpath;
+select '$.datetime("datetime template")'::jsonpath;
 
 select '$ ? (@ starts with "abc")'::jsonpath;
 select '$ ? (@ starts with $var)'::jsonpath;
0003-error-suppression-for-datetime-5.patchtext/x-patch; name=0003-error-suppression-for-datetime-5.patchDownload
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 9e291b5..fa50d79 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -550,13 +550,15 @@ date_mii(PG_FUNCTION_ARGS)
 	PG_RETURN_DATEADT(result);
 }
 
+
 /*
- * Internal routines for promoting date to timestamp and timestamp with
- * time zone
+ * Promote date to timestamp.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero is returned.
  */
-
-static Timestamp
-date2timestamp(DateADT dateVal)
+Timestamp
+date2timestamp_opt_error(DateADT dateVal, bool *have_error)
 {
 	Timestamp	result;
 
@@ -572,9 +574,19 @@ date2timestamp(DateADT dateVal)
 		 * boundary need be checked for overflow.
 		 */
 		if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("date out of range for timestamp")));
+		{
+			if (have_error)
+			{
+				*have_error = true;
+				return (Timestamp) 0;
+			}
+			else
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("date out of range for timestamp")));
+			}
+		}
 
 		/* date is days since 2000, timestamp is microseconds since same... */
 		result = dateVal * USECS_PER_DAY;
@@ -583,8 +595,23 @@ date2timestamp(DateADT dateVal)
 	return result;
 }
 
+/*
+ * Single-argument version of date2timestamp_opt_error().
+ */
 static TimestampTz
-date2timestamptz(DateADT dateVal)
+date2timestamp(DateADT dateVal)
+{
+	return date2timestamp_opt_error(dateVal, NULL);
+}
+
+/*
+ * Promote date to timestamp with time zone.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero is returned.
+ */
+TimestampTz
+date2timestamptz_opt_error(DateADT dateVal, bool *have_error)
 {
 	TimestampTz result;
 	struct pg_tm tt,
@@ -603,9 +630,19 @@ date2timestamptz(DateADT dateVal)
 		 * boundary need be checked for overflow.
 		 */
 		if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("date out of range for timestamp")));
+		{
+			if (have_error)
+			{
+				*have_error = true;
+				return (TimestampTz) 0;
+			}
+			else
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("date out of range for timestamp")));
+			}
+		}
 
 		j2date(dateVal + POSTGRES_EPOCH_JDATE,
 			   &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday));
@@ -621,15 +658,34 @@ date2timestamptz(DateADT dateVal)
 		 * of time zone, check for allowed timestamp range after adding tz.
 		 */
 		if (!IS_VALID_TIMESTAMP(result))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("date out of range for timestamp")));
+		{
+			if (have_error)
+			{
+				*have_error = true;
+				return (TimestampTz) 0;
+			}
+			else
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("date out of range for timestamp")));
+			}
+		}
 	}
 
 	return result;
 }
 
 /*
+ * Single-argument version of date2timestamptz_opt_error().
+ */
+static TimestampTz
+date2timestamptz(DateADT dateVal)
+{
+	return date2timestamptz_opt_error(dateVal, NULL);
+}
+
+/*
  * date2timestamp_no_overflow
  *
  * This is chartered to produce a double value that is numerically
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 9031432..a3b15b5 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -99,6 +99,43 @@
 #include "utils/pg_locale.h"
 
 /* ----------
+ * Convenience macros for error handling
+ * ----------
+ *
+ * Two macros below help to handle errors in functions that take
+ * 'bool *have_error' argument.  When this argument is not NULL, it's expected
+ * that function will suppress ereports when possible.  Instead it should
+ * return some default value and set *have_error flag.
+ *
+ * RETURN_ERROR() macro intended to wrap ereport() calls.  When have_error
+ * function argument is not NULL, then instead of ereport'ing we set
+ * *have_error flag and go to on_error label.  It's supposed that jump
+ * resources will be freed and some 'default' value returned.
+ *
+ * CHECK_ERROR() jumps on_error label when *have_error flag is defined and set.
+ * It's supposed to be used for immediate exit from the function on error
+ * after call of another function with 'bool *have_error' argument.
+ */
+#define RETURN_ERROR(throw_error) \
+do { \
+	if (have_error) \
+	{ \
+		*have_error = true; \
+		goto on_error; \
+	} \
+	else \
+	{ \
+		throw_error; \
+	} \
+} while (0)
+
+#define CHECK_ERROR \
+do { \
+	if (have_error && *have_error) \
+		goto on_error; \
+} while (0)
+
+/* ----------
  * Routines type
  * ----------
  */
@@ -1008,7 +1045,7 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 static void DCH_to_char(FormatNode *node, bool is_interval,
 						TmToChar *in, char *out, Oid collid);
 static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
-						  bool strict);
+						  bool strict, bool *have_error);
 
 #ifdef DEBUG_TO_FROM_CHAR
 static void dump_index(const KeyWord *k, const int *index);
@@ -1019,14 +1056,21 @@ static const char *get_th(char *num, int type);
 static char *str_numth(char *dest, char *num, int type);
 static int	adjust_partial_year_to_2020(int year);
 static int	strspace_len(char *str);
-static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode);
-static void from_char_set_int(int *dest, const int value, const FormatNode *node);
-static int	from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node);
-static int	from_char_parse_int(int *dest, char **src, FormatNode *node);
+static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode,
+							   bool *have_error);
+static void from_char_set_int(int *dest, const int value, const FormatNode *node,
+							  bool *have_error);
+static int	from_char_parse_int_len(int *dest, char **src, const int len,
+									FormatNode *node, bool *have_error);
+static int	from_char_parse_int(int *dest, char **src, FormatNode *node,
+								bool *have_error);
 static int	seq_search(char *name, const char *const *array, int type, int max, int *len);
-static int	from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
+static int	from_char_seq_search(int *dest, char **src,
+								 const char *const *array, int type, int max,
+								 FormatNode *node, bool *have_error);
 static void do_to_timestamp(text *date_txt, text *fmt, bool strict,
-							struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags);
+							struct pg_tm *tm, fsec_t *fsec, int *fprec,
+							int *flags, bool *have_error);
 static char *fill_str(char *str, int c, int max);
 static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
 static char *int_to_roman(int number);
@@ -2202,21 +2246,26 @@ strspace_len(char *str)
  *
  * Puke if the date mode has already been set, and the caller attempts to set
  * it to a conflicting mode.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
-from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode)
+from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode, bool *have_error)
 {
 	if (mode != FROM_CHAR_DATE_NONE)
 	{
 		if (tmfc->mode == FROM_CHAR_DATE_NONE)
 			tmfc->mode = mode;
 		else if (tmfc->mode != mode)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("invalid combination of date conventions"),
-					 errhint("Do not mix Gregorian and ISO week date "
-							 "conventions in a formatting template.")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("invalid combination of date conventions"),
+								  errhint("Do not mix Gregorian and ISO week date "
+										  "conventions in a formatting template."))));
 	}
+
+on_error:
+	return;
 }
 
 /*
@@ -2224,18 +2273,25 @@ from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode)
  *
  * Puke if the destination integer has previously been set to some other
  * non-zero value.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
-from_char_set_int(int *dest, const int value, const FormatNode *node)
+from_char_set_int(int *dest, const int value, const FormatNode *node,
+				  bool *have_error)
 {
 	if (*dest != 0 && *dest != value)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("conflicting values for \"%s\" field in formatting string",
-						node->key->name),
-				 errdetail("This value contradicts a previous setting for "
-						   "the same field type.")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("conflicting values for \"%s\" field in "
+									 "formatting string",
+									 node->key->name),
+							  errdetail("This value contradicts a previous setting "
+										"for the same field type."))));
 	*dest = value;
+
+on_error:
+	return;
 }
 
 /*
@@ -2257,9 +2313,13 @@ from_char_set_int(int *dest, const int value, const FormatNode *node)
  * Note that from_char_parse_int() provides a more convenient wrapper where
  * the length of the field is the same as the length of the format keyword (as
  * with DD and MI).
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and -1 is returned.
  */
 static int
-from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
+from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node,
+						bool *have_error)
 {
 	long		result;
 	char		copy[DCH_MAX_ITEM_SIZ + 1];
@@ -2292,51 +2352,60 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
 		char	   *last;
 
 		if (used < len)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("source string too short for \"%s\" formatting field",
-							node->key->name),
-					 errdetail("Field requires %d characters, but only %d "
-							   "remain.",
-							   len, used),
-					 errhint("If your source string is not fixed-width, try "
-							 "using the \"FM\" modifier.")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("source string too short for \"%s\" "
+										 "formatting field",
+										 node->key->name),
+								  errdetail("Field requires %d characters, "
+											"but only %d remain.",
+											len, used),
+								  errhint("If your source string is not fixed-width, "
+										  "try using the \"FM\" modifier."))));
 
 		errno = 0;
 		result = strtol(copy, &last, 10);
 		used = last - copy;
 
 		if (used > 0 && used < len)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("invalid value \"%s\" for \"%s\"",
-							copy, node->key->name),
-					 errdetail("Field requires %d characters, but only %d "
-							   "could be parsed.", len, used),
-					 errhint("If your source string is not fixed-width, try "
-							 "using the \"FM\" modifier.")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("invalid value \"%s\" for \"%s\"",
+										 copy, node->key->name),
+								  errdetail("Field requires %d characters, "
+											"but only %d could be parsed.",
+											len, used),
+								  errhint("If your source string is not fixed-width, "
+										  "try using the \"FM\" modifier."))));
 
 		*src += used;
 	}
 
 	if (*src == init)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("invalid value \"%s\" for \"%s\"",
-						copy, node->key->name),
-				 errdetail("Value must be an integer.")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("invalid value \"%s\" for \"%s\"",
+									 copy, node->key->name),
+							  errdetail("Value must be an integer."))));
 
 	if (errno == ERANGE || result < INT_MIN || result > INT_MAX)
-		ereport(ERROR,
-				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-				 errmsg("value for \"%s\" in source string is out of range",
-						node->key->name),
-				 errdetail("Value must be in the range %d to %d.",
-						   INT_MIN, INT_MAX)));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							  errmsg("value for \"%s\" in source string is out of range",
+									 node->key->name),
+							  errdetail("Value must be in the range %d to %d.",
+										INT_MIN, INT_MAX))));
 
 	if (dest != NULL)
-		from_char_set_int(dest, (int) result, node);
+	{
+		from_char_set_int(dest, (int) result, node, have_error);
+		CHECK_ERROR;
+	}
+
 	return *src - init;
+
+on_error:
+	return -1;
 }
 
 /*
@@ -2349,9 +2418,9 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
  * required length explicitly.
  */
 static int
-from_char_parse_int(int *dest, char **src, FormatNode *node)
+from_char_parse_int(int *dest, char **src, FormatNode *node, bool *have_error)
 {
-	return from_char_parse_int_len(dest, src, node->key->len, node);
+	return from_char_parse_int_len(dest, src, node->key->len, node, have_error);
 }
 
 /* ----------
@@ -2434,11 +2503,12 @@ seq_search(char *name, const char *const *array, int type, int max, int *len)
  * pointed to by 'dest', advance 'src' to the end of the part of the string
  * which matched, and return the number of characters consumed.
  *
- * If the string doesn't match, throw an error.
+ * If the string doesn't match, throw an error if 'have_error' is NULL,
+ * otherwise set '*have_error' and return -1.
  */
 static int
-from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max,
-					 FormatNode *node)
+from_char_seq_search(int *dest, char **src, const char *const *array, int type,
+					 int max, FormatNode *node, bool *have_error)
 {
 	int			len;
 
@@ -2450,15 +2520,18 @@ from_char_seq_search(int *dest, char **src, const char *const *array, int type,
 		Assert(max <= DCH_MAX_ITEM_SIZ);
 		strlcpy(copy, *src, max + 1);
 
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("invalid value \"%s\" for \"%s\"",
-						copy, node->key->name),
-				 errdetail("The given value did not match any of the allowed "
-						   "values for this field.")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("invalid value \"%s\" for \"%s\"",
+									 copy, node->key->name),
+							  errdetail("The given value did not match any of "
+										"the allowed values for this field."))));
 	}
 	*src += len;
 	return len;
+
+on_error:
+	return -1;
 }
 
 /* ----------
@@ -3060,10 +3133,13 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
  *
  * Note: we currently don't have any to_interval() function, so there
  * is no need here for INVALID_FOR_INTERVAL checks.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  * ----------
  */
 static void
-DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
+DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict,
+			  bool *have_error)
 {
 	FormatNode *n;
 	char	   *s;
@@ -3146,7 +3222,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 			continue;
 		}
 
-		from_char_set_mode(out, n->key->date_mode);
+		from_char_set_mode(out, n->key->date_mode, have_error);
+		CHECK_ERROR;
 
 		switch (n->key->id)
 		{
@@ -3158,8 +3235,10 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 			case DCH_a_m:
 			case DCH_p_m:
 				from_char_seq_search(&value, &s, ampm_strings_long,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->pm, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->pm, value % 2, n, have_error);
+				CHECK_ERROR;
 				out->clock = CLOCK_12_HOUR;
 				break;
 			case DCH_AM:
@@ -3167,30 +3246,37 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 			case DCH_am:
 			case DCH_pm:
 				from_char_seq_search(&value, &s, ampm_strings,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->pm, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->pm, value % 2, n, have_error);
+				CHECK_ERROR;
 				out->clock = CLOCK_12_HOUR;
 				break;
 			case DCH_HH:
 			case DCH_HH12:
-				from_char_parse_int_len(&out->hh, &s, 2, n);
+				from_char_parse_int_len(&out->hh, &s, 2, n, have_error);
+				CHECK_ERROR;
 				out->clock = CLOCK_12_HOUR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_HH24:
-				from_char_parse_int_len(&out->hh, &s, 2, n);
+				from_char_parse_int_len(&out->hh, &s, 2, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_MI:
-				from_char_parse_int(&out->mi, &s, n);
+				from_char_parse_int(&out->mi, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_SS:
-				from_char_parse_int(&out->ss, &s, n);
+				from_char_parse_int(&out->ss, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_MS:		/* millisecond */
-				len = from_char_parse_int_len(&out->ms, &s, 3, n);
+				len = from_char_parse_int_len(&out->ms, &s, 3, n, have_error);
+				CHECK_ERROR;
 
 				/*
 				 * 25 is 0.25 and 250 is 0.25 too; 025 is 0.025 and not 0.25
@@ -3211,7 +3297,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 			case DCH_US:		/* microsecond */
 				len = from_char_parse_int_len(&out->us, &s,
 											  n->key->id == DCH_US ? 6 :
-											  out->ff, n);
+											  out->ff, n, have_error);
+				CHECK_ERROR;
 
 				out->us *= len == 1 ? 100000 :
 					len == 2 ? 10000 :
@@ -3222,16 +3309,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_SSSS:
-				from_char_parse_int(&out->ssss, &s, n);
+				from_char_parse_int(&out->ssss, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_tz:
 			case DCH_TZ:
 			case DCH_OF:
-				ereport(ERROR,
-						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						 errmsg("formatting field \"%s\" is only supported in to_char",
-								n->key->name)));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									  errmsg("formatting field \"%s\" is only supported in to_char",
+											 n->key->name))));
+				CHECK_ERROR;
 				break;
 			case DCH_TZH:
 
@@ -3255,82 +3344,102 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 						out->tzsign = +1;
 				}
 
-				from_char_parse_int_len(&out->tzh, &s, 2, n);
+				from_char_parse_int_len(&out->tzh, &s, 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_TZM:
 				/* assign positive timezone sign if TZH was not seen before */
 				if (!out->tzsign)
 					out->tzsign = +1;
-				from_char_parse_int_len(&out->tzm, &s, 2, n);
+				from_char_parse_int_len(&out->tzm, &s, 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_A_D:
 			case DCH_B_C:
 			case DCH_a_d:
 			case DCH_b_c:
 				from_char_seq_search(&value, &s, adbc_strings_long,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->bc, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->bc, value % 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_AD:
 			case DCH_BC:
 			case DCH_ad:
 			case DCH_bc:
 				from_char_seq_search(&value, &s, adbc_strings,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->bc, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->bc, value % 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_MONTH:
 			case DCH_Month:
 			case DCH_month:
 				from_char_seq_search(&value, &s, months_full, ONE_UPPER,
-									 MAX_MONTH_LEN, n);
-				from_char_set_int(&out->mm, value + 1, n);
+									 MAX_MONTH_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, value + 1, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_MON:
 			case DCH_Mon:
 			case DCH_mon:
 				from_char_seq_search(&value, &s, months, ONE_UPPER,
-									 MAX_MON_LEN, n);
-				from_char_set_int(&out->mm, value + 1, n);
+									 MAX_MON_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, value + 1, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_MM:
-				from_char_parse_int(&out->mm, &s, n);
+				from_char_parse_int(&out->mm, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_DAY:
 			case DCH_Day:
 			case DCH_day:
 				from_char_seq_search(&value, &s, days, ONE_UPPER,
-									 MAX_DAY_LEN, n);
-				from_char_set_int(&out->d, value, n);
+									 MAX_DAY_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->d, value, n, have_error);
+				CHECK_ERROR;
 				out->d++;
 				break;
 			case DCH_DY:
 			case DCH_Dy:
 			case DCH_dy:
 				from_char_seq_search(&value, &s, days, ONE_UPPER,
-									 MAX_DY_LEN, n);
-				from_char_set_int(&out->d, value, n);
+									 MAX_DY_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->d, value, n, have_error);
+				CHECK_ERROR;
 				out->d++;
 				break;
 			case DCH_DDD:
-				from_char_parse_int(&out->ddd, &s, n);
+				from_char_parse_int(&out->ddd, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_IDDD:
-				from_char_parse_int_len(&out->ddd, &s, 3, n);
+				from_char_parse_int_len(&out->ddd, &s, 3, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_DD:
-				from_char_parse_int(&out->dd, &s, n);
+				from_char_parse_int(&out->dd, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_D:
-				from_char_parse_int(&out->d, &s, n);
+				from_char_parse_int(&out->d, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_ID:
-				from_char_parse_int_len(&out->d, &s, 1, n);
+				from_char_parse_int_len(&out->d, &s, 1, n, have_error);
+				CHECK_ERROR;
 				/* Shift numbering to match Gregorian where Sunday = 1 */
 				if (++out->d > 7)
 					out->d = 1;
@@ -3338,7 +3447,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 				break;
 			case DCH_WW:
 			case DCH_IW:
-				from_char_parse_int(&out->ww, &s, n);
+				from_char_parse_int(&out->ww, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Q:
@@ -3353,11 +3463,13 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 				 * We still parse the source string for an integer, but it
 				 * isn't stored anywhere in 'out'.
 				 */
-				from_char_parse_int((int *) NULL, &s, n);
+				from_char_parse_int((int *) NULL, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_CC:
-				from_char_parse_int(&out->cc, &s, n);
+				from_char_parse_int(&out->cc, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Y_YYY:
@@ -3369,11 +3481,12 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 
 					matched = sscanf(s, "%d,%03d%n", &millennia, &years, &nch);
 					if (matched < 2)
-						ereport(ERROR,
-								(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-								 errmsg("invalid input string for \"Y,YYY\"")));
+						RETURN_ERROR(ereport(ERROR,
+											 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+											  errmsg("invalid input string for \"Y,YYY\""))));
 					years += (millennia * 1000);
-					from_char_set_int(&out->year, years, n);
+					from_char_set_int(&out->year, years, n, have_error);
+					CHECK_ERROR;
 					out->yysz = 4;
 					s += nch;
 					SKIP_THth(s, n->suffix);
@@ -3381,47 +3494,62 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 				break;
 			case DCH_YYYY:
 			case DCH_IYYY:
-				from_char_parse_int(&out->year, &s, n);
+				from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
 				out->yysz = 4;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_YYY:
 			case DCH_IYY:
-				if (from_char_parse_int(&out->year, &s, n) < 4)
+				len = from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
+				if (len < 4)
 					out->year = adjust_partial_year_to_2020(out->year);
 				out->yysz = 3;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_YY:
 			case DCH_IY:
-				if (from_char_parse_int(&out->year, &s, n) < 4)
+				len = from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
+				if (len < 4)
 					out->year = adjust_partial_year_to_2020(out->year);
 				out->yysz = 2;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Y:
 			case DCH_I:
-				if (from_char_parse_int(&out->year, &s, n) < 4)
+				len = from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
+				if (len < 4)
 					out->year = adjust_partial_year_to_2020(out->year);
 				out->yysz = 1;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_RM:
 				from_char_seq_search(&value, &s, rm_months_upper,
-									 ALL_UPPER, MAX_RM_LEN, n);
-				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n);
+									 ALL_UPPER, MAX_RM_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value,
+								  n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_rm:
 				from_char_seq_search(&value, &s, rm_months_lower,
-									 ALL_LOWER, MAX_RM_LEN, n);
-				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n);
+									 ALL_LOWER, MAX_RM_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value,
+								  n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_W:
-				from_char_parse_int(&out->w, &s, n);
+				from_char_parse_int(&out->w, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_J:
-				from_char_parse_int(&out->j, &s, n);
+				from_char_parse_int(&out->j, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 		}
@@ -3441,19 +3569,22 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 	if (strict)
 	{
 		if (n->type != NODE_TYPE_END)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("input string is too short for datetime format")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("input string is too short for datetime format"))));
 
 		while (*s != '\0' && isspace((unsigned char) *s))
 			s++;
 
 		if (*s != '\0')
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("trailing characters remain in input string after "
-							"datetime format")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("trailing characters remain in input string "
+										 "after datetime format"))));
 	}
+
+on_error:
+	return;
 }
 
 /*
@@ -3474,9 +3605,13 @@ DCH_prevent_counter_overflow(void)
 	}
 }
 
-/* Get mask of date/time/zone components present in format nodes. */
+/*
+ * Get mask of date/time/zone components present in format nodes.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
+ */
 static int
-DCH_datetime_type(FormatNode *node)
+DCH_datetime_type(FormatNode *node, bool *have_error)
 {
 	FormatNode *n;
 	int			flags = 0;
@@ -3517,10 +3652,10 @@ DCH_datetime_type(FormatNode *node)
 			case DCH_tz:
 			case DCH_TZ:
 			case DCH_OF:
-				ereport(ERROR,
-						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						 errmsg("formatting field \"%s\" is only supported in to_char",
-								n->key->name)));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									  errmsg("formatting field \"%s\" is only supported in to_char",
+											 n->key->name))));
 				flags |= DCH_ZONED;
 				break;
 			case DCH_TZH:
@@ -3574,6 +3709,7 @@ DCH_datetime_type(FormatNode *node)
 		}
 	}
 
+on_error:
 	return flags;
 }
 
@@ -3867,7 +4003,7 @@ to_timestamp(PG_FUNCTION_ARGS)
 	fsec_t		fsec;
 	int			fprec;
 
-	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL, NULL);
 
 	/* Use the specified time zone, if any. */
 	if (tm.tm_zone)
@@ -3906,7 +4042,7 @@ to_date(PG_FUNCTION_ARGS)
 	struct pg_tm tm;
 	fsec_t		fsec;
 
-	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL, NULL);
 
 	/* Prevent overflow in Julian-day routines */
 	if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3933,17 +4069,21 @@ to_date(PG_FUNCTION_ARGS)
  * the presence of date/time/zone components in the format string.
  *
  * When timezone component is present, the corresponding offset is set to '*tz'.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero value is returned.
  */
 Datum
 parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
-			   int32 *typmod, int *tz)
+			   int32 *typmod, int *tz, bool *have_error)
 {
 	struct pg_tm tm;
 	fsec_t		fsec;
 	int			fprec = 0;
 	int			flags;
 
-	do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags);
+	do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags, have_error);
+	CHECK_ERROR;
 
 	*typmod = fprec ? fprec : -1;	/* fractional part precision */
 
@@ -3964,16 +4104,22 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 				}
 				else
 				{
-					if (*tz == PG_INT32_MIN)
-						ereport(ERROR,
-								(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-								 errmsg("missing time zone in input string for type timestamptz")));
+					/*
+					 * Time zone is present in format string, but not in input
+					 * string.  Assuming do_to_timestamp() triggers no error
+					 * this should be possible only in non-strict case.
+					 */
+					Assert(!strict);
+
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+										  errmsg("missing time zone in input string for type timestamptz"))));
 				}
 
 				if (tm2timestamp(&tm, fsec, tz, &result) != 0)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("timestamptz out of range")));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("timestamptz out of range"))));
 
 				AdjustTimestampForTypmod(&result, *typmod);
 
@@ -3985,9 +4131,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 				Timestamp	result;
 
 				if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("timestamp out of range")));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("timestamp out of range"))));
 
 				AdjustTimestampForTypmod(&result, *typmod);
 
@@ -3999,9 +4145,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 		{
 			if (flags & DCH_ZONED)
 			{
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-						 errmsg("datetime format is zoned but not timed")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+									  errmsg("datetime format is zoned but not timed"))));
 			}
 			else
 			{
@@ -4009,20 +4155,20 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 
 				/* Prevent overflow in Julian-day routines */
 				if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("date out of range: \"%s\"",
-									text_to_cstring(date_txt))));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("date out of range: \"%s\"",
+												 text_to_cstring(date_txt)))));
 
 				result = 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(result))
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("date out of range: \"%s\"",
-									text_to_cstring(date_txt))));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("date out of range: \"%s\"",
+												 text_to_cstring(date_txt)))));
 
 				*typid = DATEOID;
 				return DateADTGetDatum(result);
@@ -4040,20 +4186,26 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 				int			dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
 
 				if (dterr)
-					DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz");
+					RETURN_ERROR(DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz"));
 			}
 			else
 			{
-				if (*tz == PG_INT32_MIN)
-					ereport(ERROR,
-							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-							 errmsg("missing time zone in input string for type timetz")));
+				/*
+				 * Time zone is present in format string, but not in input
+				 * string.  Assuming do_to_timestamp() triggers no error this
+				 * should be possible only in non-strict case.
+				 */
+				Assert(!strict);
+
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+									  errmsg("missing time zone in input string for type timetz"))));
 			}
 
 			if (tm2timetz(&tm, fsec, *tz, result) != 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-						 errmsg("timetz out of range")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+									  errmsg("timetz out of range"))));
 
 			AdjustTimeForTypmod(&result->time, *typmod);
 
@@ -4065,9 +4217,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 			TimeADT		result;
 
 			if (tm2time(&tm, fsec, &result) != 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-						 errmsg("time out of range")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+									  errmsg("time out of range"))));
 
 			AdjustTimeForTypmod(&result, *typmod);
 
@@ -4077,11 +4229,12 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 	}
 	else
 	{
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("datetime format is not dated and not timed")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("datetime format is not dated and not timed"))));
 	}
 
+on_error:
 	return (Datum) 0;
 }
 
@@ -4102,16 +4255,19 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
  *
  * 'strict' enables error reporting on unmatched trailing characters in input
  * or format string patterns.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
-do_to_timestamp(text *date_txt, text *fmt, bool strict,
-				struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags)
+do_to_timestamp(text *date_txt, text *fmt, bool strict, struct pg_tm *tm,
+				fsec_t *fsec, int *fprec, int *flags, bool *have_error)
 {
-	FormatNode *format;
+	FormatNode *format = NULL;
 	TmFromChar	tmfc;
 	int			fmt_len;
 	char	   *date_str;
 	int			fmask;
+	bool		incache = false;
 
 	date_str = text_to_cstring(date_txt);
 
@@ -4125,7 +4281,6 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 	if (fmt_len)
 	{
 		char	   *fmt_str;
-		bool		incache;
 
 		fmt_str = text_to_cstring(fmt);
 
@@ -4135,8 +4290,6 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 			 * Allocate new memory if format picture is bigger than static
 			 * cache and do not use cache (call parser always)
 			 */
-			incache = false;
-
 			format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode));
 
 			parse_format(format, fmt_str, DCH_keywords,
@@ -4158,15 +4311,21 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 		/* dump_index(DCH_keywords, DCH_index); */
 #endif
 
-		DCH_from_char(format, date_str, &tmfc, strict);
+		DCH_from_char(format, date_str, &tmfc, strict, have_error);
+		CHECK_ERROR;
 
 		pfree(fmt_str);
 
 		if (flags)
-			*flags = DCH_datetime_type(format);
+			*flags = DCH_datetime_type(format, have_error);
 
 		if (!incache)
+		{
 			pfree(format);
+			format = NULL;
+		}
+
+		CHECK_ERROR;
 	}
 
 	DEBUG_TMFC(&tmfc);
@@ -4195,11 +4354,13 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 	if (tmfc.clock == CLOCK_12_HOUR)
 	{
 		if (tm->tm_hour < 1 || tm->tm_hour > HOURS_PER_DAY / 2)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("hour \"%d\" is invalid for the 12-hour clock",
-							tm->tm_hour),
-					 errhint("Use the 24-hour clock, or give an hour between 1 and 12.")));
+		{
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("hour \"%d\" is invalid for the 12-hour clock",
+										 tm->tm_hour),
+								  errhint("Use the 24-hour clock, or give an hour between 1 and 12."))));
+		}
 
 		if (tmfc.pm && tm->tm_hour < HOURS_PER_DAY / 2)
 			tm->tm_hour += HOURS_PER_DAY / 2;
@@ -4303,9 +4464,11 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 		 */
 
 		if (!tm->tm_year && !tmfc.bc)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("cannot calculate day of year without year information")));
+		{
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("cannot calculate day of year without year information"))));
+		}
 
 		if (tmfc.mode == FROM_CHAR_DATE_ISOWEEK)
 		{
@@ -4362,7 +4525,7 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 			 * said DTERR_MD_FIELD_OVERFLOW, because we don't want to print an
 			 * irrelevant hint about datestyle.
 			 */
-			DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp");
+			RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp"));
 		}
 	}
 
@@ -4371,7 +4534,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 		tm->tm_min < 0 || tm->tm_min >= MINS_PER_HOUR ||
 		tm->tm_sec < 0 || tm->tm_sec >= SECS_PER_MINUTE ||
 		*fsec < INT64CONST(0) || *fsec >= USECS_PER_SEC)
-		DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp");
+	{
+		RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp"));
+	}
 
 	/* Save parsed time-zone into tm->tm_zone if it was specified */
 	if (tmfc.tzsign)
@@ -4380,7 +4545,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 
 		if (tmfc.tzh < 0 || tmfc.tzh > MAX_TZDISP_HOUR ||
 			tmfc.tzm < 0 || tmfc.tzm >= MINS_PER_HOUR)
-			DateTimeParseError(DTERR_TZDISP_OVERFLOW, date_str, "timestamp");
+		{
+			RETURN_ERROR(DateTimeParseError(DTERR_TZDISP_OVERFLOW, date_str, "timestamp"));
+		}
 
 		tz = psprintf("%c%02d:%02d",
 					  tmfc.tzsign > 0 ? '+' : '-', tmfc.tzh, tmfc.tzm);
@@ -4390,6 +4557,11 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 
 	DEBUG_TM(tm);
 
+on_error:
+
+	if (format && !incache)
+		pfree(format);
+
 	pfree(date_str);
 }
 
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 2931bd5..84bc97d 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -329,11 +329,11 @@ timestamp_scale(PG_FUNCTION_ARGS)
 }
 
 /*
- * AdjustTimestampForTypmod --- round off a timestamp to suit given typmod
+ * AdjustTimestampForTypmodError --- round off a timestamp to suit given typmod
  * Works for either timestamp or timestamptz.
  */
-void
-AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
+bool
+AdjustTimestampForTypmodError(Timestamp *time, int32 typmod, bool *error)
 {
 	static const int64 TimestampScales[MAX_TIMESTAMP_PRECISION + 1] = {
 		INT64CONST(1000000),
@@ -359,10 +359,18 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
 		&& (typmod != -1) && (typmod != MAX_TIMESTAMP_PRECISION))
 	{
 		if (typmod < 0 || typmod > MAX_TIMESTAMP_PRECISION)
+		{
+			if (error)
+			{
+				*error = true;
+				return false;
+			}
+
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("timestamp(%d) precision must be between %d and %d",
 							typmod, 0, MAX_TIMESTAMP_PRECISION)));
+		}
 
 		if (*time >= INT64CONST(0))
 		{
@@ -375,8 +383,15 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
 					  * TimestampScales[typmod]);
 		}
 	}
+
+	return true;
 }
 
+void
+AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
+{
+	(void) AdjustTimestampForTypmodError(time, typmod, NULL);
+}
 
 /* timestamptz_in()
  * Convert a string to internal form.
@@ -5172,8 +5187,15 @@ timestamp_timestamptz(PG_FUNCTION_ARGS)
 	PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
 }
 
-static TimestampTz
-timestamp2timestamptz(Timestamp timestamp)
+/*
+ * Convert timestamp to timestamp with time zone.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero is returned.
+ */
+
+TimestampTz
+timestamp2timestamptz_opt_error(Timestamp timestamp, bool *have_error)
 {
 	TimestampTz result;
 	struct pg_tm tt,
@@ -5182,23 +5204,33 @@ timestamp2timestamptz(Timestamp timestamp)
 	int			tz;
 
 	if (TIMESTAMP_NOT_FINITE(timestamp))
-		result = timestamp;
-	else
-	{
-		if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("timestamp out of range")));
+		return timestamp;
 
+	if (!timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL))
+	{
 		tz = DetermineTimeZoneOffset(tm, session_timezone);
 
-		if (tm2timestamp(tm, fsec, &tz, &result) != 0)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("timestamp out of range")));
+		if (!tm2timestamp(tm, fsec, &tz, &result))
+			return result;
 	}
 
-	return result;
+	if (have_error)
+		*have_error = true;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+				 errmsg("timestamp out of range")));
+
+	return 0;
+}
+
+/*
+ * Single-argument version of timestamp2timestamptz_opt_error().
+ */
+static TimestampTz
+timestamp2timestamptz(Timestamp timestamp)
+{
+	return timestamp2timestamptz_opt_error(timestamp, NULL);
 }
 
 /* timestamptz_timestamp()
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index bd15bfa..c29f13a 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -70,6 +70,8 @@ typedef struct
 /* date.c */
 extern int32 anytime_typmod_check(bool istz, int32 typmod);
 extern double date2timestamp_no_overflow(DateADT dateVal);
+extern Timestamp date2timestamp_opt_error(DateADT dateVal, bool *have_error);
+extern TimestampTz date2timestamptz_opt_error(DateADT dateVal, bool *have_error);
 extern void EncodeSpecialDate(DateADT dt, char *str);
 extern DateADT GetSQLCurrentDate(void);
 extern TimeTzADT *GetSQLCurrentTime(int32 typmod);
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index 0cafdd2..5ebf336 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -337,5 +337,7 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
 extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
 
 extern void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
+extern bool AdjustTimestampForTypmodError(Timestamp *time, int32 typmod,
+										  bool *error);
 
 #endif							/* DATETIME_H */
diff --git a/src/include/utils/formatting.h b/src/include/utils/formatting.h
index a390302..1a2e2a9 100644
--- a/src/include/utils/formatting.h
+++ b/src/include/utils/formatting.h
@@ -29,6 +29,7 @@ extern char *asc_toupper(const char *buff, size_t nbytes);
 extern char *asc_initcap(const char *buff, size_t nbytes);
 
 extern Datum parse_datetime(text *date_txt, text *fmt, bool strict,
-							Oid *typid, int32 *typmod, int *tz);
+							Oid *typid, int32 *typmod, int *tz,
+							bool *have_error);
 
 #endif
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index ea16190..e884d44 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -97,6 +97,9 @@ extern int	timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
 /* timestamp comparison works for timestamptz also */
 #define timestamptz_cmp_internal(dt1,dt2)	timestamp_cmp_internal(dt1, dt2)
 
+extern TimestampTz timestamp2timestamptz_opt_error(Timestamp timestamp,
+												   bool *have_error);
+
 extern int	isoweek2j(int year, int week);
 extern void isoweek2date(int woy, int *year, int *mon, int *mday);
 extern void isoweekdate2date(int isoweek, int wday, int *year, int *mon, int *mday);
0002-datetime-conversion-for-jsonpath-5.patchtext/x-patch; name=0002-datetime-conversion-for-jsonpath-5.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index c2f5a75..afde816 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -6123,6 +6123,30 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
         <entry>microsecond (000000-999999)</entry>
        </row>
        <row>
+        <entry><literal>FF1</literal></entry>
+        <entry>decisecond (0-9)</entry>
+       </row>
+       <row>
+        <entry><literal>FF2</literal></entry>
+        <entry>centisecond (00-99)</entry>
+       </row>
+       <row>
+        <entry><literal>FF3</literal></entry>
+        <entry>millisecond (000-999)</entry>
+       </row>
+       <row>
+        <entry><literal>FF4</literal></entry>
+        <entry>tenth of a millisecond (0000-9999)</entry>
+       </row>
+       <row>
+        <entry><literal>FF5</literal></entry>
+        <entry>hundredth of a millisecond (00000-99999)</entry>
+       </row>
+       <row>
+        <entry><literal>FF6</literal></entry>
+        <entry>microsecond (000000-999999)</entry>
+       </row>
+       <row>
         <entry><literal>SSSS</literal></entry>
         <entry>seconds past midnight (0-86399)</entry>
        </row>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 4b1afb1..9e291b5 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -41,11 +41,6 @@
 #endif
 
 
-static int	tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
-static int	tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
-static void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
-
-
 /* common code for timetypmodin and timetztypmodin */
 static int32
 anytime_typmodin(bool istz, ArrayType *ta)
@@ -1203,7 +1198,7 @@ time_in(PG_FUNCTION_ARGS)
 /* tm2time()
  * Convert a tm structure to a time data type.
  */
-static int
+int
 tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result)
 {
 	*result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec)
@@ -1379,7 +1374,7 @@ time_scale(PG_FUNCTION_ARGS)
  * have a fundamental tie together but rather a coincidence of
  * implementation. - thomas
  */
-static void
+void
 AdjustTimeForTypmod(TimeADT *time, int32 typmod)
 {
 	static const int64 TimeScales[MAX_TIME_PRECISION + 1] = {
@@ -1957,7 +1952,7 @@ time_part(PG_FUNCTION_ARGS)
 /* tm2timetz()
  * Convert a tm structure to a time data type.
  */
-static int
+int
 tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result)
 {
 	result->time = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) *
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 206576d..9031432 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -86,6 +86,7 @@
 #endif
 
 #include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
 #include "mb/pg_wchar.h"
 #include "utils/builtins.h"
 #include "utils/date.h"
@@ -436,7 +437,8 @@ typedef struct
 				clock,			/* 12 or 24 hour clock? */
 				tzsign,			/* +1, -1 or 0 if timezone info is absent */
 				tzh,
-				tzm;
+				tzm,
+				ff;				/* fractional precision */
 } TmFromChar;
 
 #define ZERO_tmfc(_X) memset(_X, 0, sizeof(TmFromChar))
@@ -596,6 +598,12 @@ typedef enum
 	DCH_Day,
 	DCH_Dy,
 	DCH_D,
+	DCH_FF1,
+	DCH_FF2,
+	DCH_FF3,
+	DCH_FF4,
+	DCH_FF5,
+	DCH_FF6,
 	DCH_FX,						/* global suffix */
 	DCH_HH24,
 	DCH_HH12,
@@ -645,6 +653,12 @@ typedef enum
 	DCH_dd,
 	DCH_dy,
 	DCH_d,
+	DCH_ff1,
+	DCH_ff2,
+	DCH_ff3,
+	DCH_ff4,
+	DCH_ff5,
+	DCH_ff6,
 	DCH_fx,
 	DCH_hh24,
 	DCH_hh12,
@@ -745,7 +759,13 @@ static const KeyWord DCH_keywords[] = {
 	{"Day", 3, DCH_Day, false, FROM_CHAR_DATE_NONE},
 	{"Dy", 2, DCH_Dy, false, FROM_CHAR_DATE_NONE},
 	{"D", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
-	{"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},	/* F */
+	{"FF1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE},	/* F */
+	{"FF2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE},
+	{"FF3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE},
+	{"FF4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE},
+	{"FF5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE},
+	{"FF6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE},
+	{"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
 	{"HH24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE},	/* H */
 	{"HH12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
 	{"HH", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
@@ -794,7 +814,13 @@ static const KeyWord DCH_keywords[] = {
 	{"dd", 2, DCH_DD, true, FROM_CHAR_DATE_GREGORIAN},
 	{"dy", 2, DCH_dy, false, FROM_CHAR_DATE_NONE},
 	{"d", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
-	{"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},	/* f */
+	{"ff1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE},	/* f */
+	{"ff2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE},
+	{"ff3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE},
+	{"ff4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE},
+	{"ff5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE},
+	{"ff6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE},
+	{"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
 	{"hh24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE},	/* h */
 	{"hh12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
 	{"hh", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
@@ -895,10 +921,10 @@ static const int DCH_index[KeyWord_INDEX_SIZE] = {
 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 	-1, -1, -1, -1, -1, DCH_A_D, DCH_B_C, DCH_CC, DCH_DAY, -1,
-	DCH_FX, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
+	DCH_FF1, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
 	DCH_P_M, DCH_Q, DCH_RM, DCH_SSSS, DCH_TZH, DCH_US, -1, DCH_WW, -1, DCH_Y_YYY,
 	-1, -1, -1, -1, -1, -1, -1, DCH_a_d, DCH_b_c, DCH_cc,
-	DCH_day, -1, DCH_fx, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
+	DCH_day, -1, DCH_ff1, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
 	-1, -1, DCH_p_m, DCH_q, DCH_rm, DCH_ssss, DCH_tz, DCH_us, -1, DCH_ww,
 	-1, DCH_y_yyy, -1, -1, -1, -1
 
@@ -962,6 +988,10 @@ typedef struct NUMProc
 			   *L_currency_symbol;
 } NUMProc;
 
+/* Return flags for DCH_from_char() */
+#define DCH_DATED	0x01
+#define DCH_TIMED	0x02
+#define DCH_ZONED	0x04
 
 /* ----------
  * Functions
@@ -977,7 +1007,8 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 
 static void DCH_to_char(FormatNode *node, bool is_interval,
 						TmToChar *in, char *out, Oid collid);
-static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out);
+static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
+						  bool strict);
 
 #ifdef DEBUG_TO_FROM_CHAR
 static void dump_index(const KeyWord *k, const int *index);
@@ -994,8 +1025,8 @@ static int	from_char_parse_int_len(int *dest, char **src, const int len, FormatN
 static int	from_char_parse_int(int *dest, char **src, FormatNode *node);
 static int	seq_search(char *name, const char *const *array, int type, int max, int *len);
 static int	from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
-static void do_to_timestamp(text *date_txt, text *fmt,
-							struct pg_tm *tm, fsec_t *fsec);
+static void do_to_timestamp(text *date_txt, text *fmt, bool strict,
+							struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags);
 static char *fill_str(char *str, int c, int max);
 static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
 static char *int_to_roman(int number);
@@ -2517,18 +2548,32 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 					str_numth(s, s, S_TH_TYPE(n->suffix));
 				s += strlen(s);
 				break;
-			case DCH_MS:		/* millisecond */
-				sprintf(s, "%03d", (int) (in->fsec / INT64CONST(1000)));
-				if (S_THth(n->suffix))
-					str_numth(s, s, S_TH_TYPE(n->suffix));
+#define DCH_to_char_fsec(frac_fmt, frac_val) \
+				sprintf(s, frac_fmt, (int) (frac_val)); \
+				if (S_THth(n->suffix)) \
+					str_numth(s, s, S_TH_TYPE(n->suffix)); \
 				s += strlen(s);
+			case DCH_FF1:		/* decisecond */
+				DCH_to_char_fsec("%01d", in->fsec / 100000);
+				break;
+			case DCH_FF2:		/* centisecond */
+				DCH_to_char_fsec("%02d", in->fsec / 10000);
+				break;
+			case DCH_FF3:
+			case DCH_MS:		/* millisecond */
+				DCH_to_char_fsec("%03d", in->fsec / 1000);
+				break;
+			case DCH_FF4:
+				DCH_to_char_fsec("%04d", in->fsec / 100);
 				break;
+			case DCH_FF5:
+				DCH_to_char_fsec("%05d", in->fsec / 10);
+				break;
+			case DCH_FF6:
 			case DCH_US:		/* microsecond */
-				sprintf(s, "%06d", (int) in->fsec);
-				if (S_THth(n->suffix))
-					str_numth(s, s, S_TH_TYPE(n->suffix));
-				s += strlen(s);
+				DCH_to_char_fsec("%06d", in->fsec);
 				break;
+#undef DCH_to_char_fsec
 			case DCH_SSSS:
 				sprintf(s, "%d", tm->tm_hour * SECS_PER_HOUR +
 						tm->tm_min * SECS_PER_MINUTE +
@@ -3010,13 +3055,15 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 /* ----------
  * Process a string as denoted by a list of FormatNodes.
  * The TmFromChar struct pointed to by 'out' is populated with the results.
+ * 'strict' enables error reporting on unmatched trailing characters in input
+ * or format string patterns.
  *
  * Note: we currently don't have any to_interval() function, so there
  * is no need here for INVALID_FOR_INTERVAL checks.
  * ----------
  */
 static void
-DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
+DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 {
 	FormatNode *n;
 	char	   *s;
@@ -3153,8 +3200,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 
 				SKIP_THth(s, n->suffix);
 				break;
+			case DCH_FF1:
+			case DCH_FF2:
+			case DCH_FF3:
+			case DCH_FF4:
+			case DCH_FF5:
+			case DCH_FF6:
+				out->ff = n->key->id - DCH_FF1 + 1;
+				/* fall through */
 			case DCH_US:		/* microsecond */
-				len = from_char_parse_int_len(&out->us, &s, 6, n);
+				len = from_char_parse_int_len(&out->us, &s,
+											  n->key->id == DCH_US ? 6 :
+											  out->ff, n);
 
 				out->us *= len == 1 ? 100000 :
 					len == 2 ? 10000 :
@@ -3380,6 +3437,23 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 			}
 		}
 	}
+
+	if (strict)
+	{
+		if (n->type != NODE_TYPE_END)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+					 errmsg("input string is too short for datetime format")));
+
+		while (*s != '\0' && isspace((unsigned char) *s))
+			s++;
+
+		if (*s != '\0')
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+					 errmsg("trailing characters remain in input string after "
+							"datetime format")));
+	}
 }
 
 /*
@@ -3400,6 +3474,109 @@ DCH_prevent_counter_overflow(void)
 	}
 }
 
+/* Get mask of date/time/zone components present in format nodes. */
+static int
+DCH_datetime_type(FormatNode *node)
+{
+	FormatNode *n;
+	int			flags = 0;
+
+	for (n = node; n->type != NODE_TYPE_END; n++)
+	{
+		if (n->type != NODE_TYPE_ACTION)
+			continue;
+
+		switch (n->key->id)
+		{
+			case DCH_FX:
+				break;
+			case DCH_A_M:
+			case DCH_P_M:
+			case DCH_a_m:
+			case DCH_p_m:
+			case DCH_AM:
+			case DCH_PM:
+			case DCH_am:
+			case DCH_pm:
+			case DCH_HH:
+			case DCH_HH12:
+			case DCH_HH24:
+			case DCH_MI:
+			case DCH_SS:
+			case DCH_MS:		/* millisecond */
+			case DCH_US:		/* microsecond */
+			case DCH_FF1:
+			case DCH_FF2:
+			case DCH_FF3:
+			case DCH_FF4:
+			case DCH_FF5:
+			case DCH_FF6:
+			case DCH_SSSS:
+				flags |= DCH_TIMED;
+				break;
+			case DCH_tz:
+			case DCH_TZ:
+			case DCH_OF:
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("formatting field \"%s\" is only supported in to_char",
+								n->key->name)));
+				flags |= DCH_ZONED;
+				break;
+			case DCH_TZH:
+			case DCH_TZM:
+				flags |= DCH_ZONED;
+				break;
+			case DCH_A_D:
+			case DCH_B_C:
+			case DCH_a_d:
+			case DCH_b_c:
+			case DCH_AD:
+			case DCH_BC:
+			case DCH_ad:
+			case DCH_bc:
+			case DCH_MONTH:
+			case DCH_Month:
+			case DCH_month:
+			case DCH_MON:
+			case DCH_Mon:
+			case DCH_mon:
+			case DCH_MM:
+			case DCH_DAY:
+			case DCH_Day:
+			case DCH_day:
+			case DCH_DY:
+			case DCH_Dy:
+			case DCH_dy:
+			case DCH_DDD:
+			case DCH_IDDD:
+			case DCH_DD:
+			case DCH_D:
+			case DCH_ID:
+			case DCH_WW:
+			case DCH_Q:
+			case DCH_CC:
+			case DCH_Y_YYY:
+			case DCH_YYYY:
+			case DCH_IYYY:
+			case DCH_YYY:
+			case DCH_IYY:
+			case DCH_YY:
+			case DCH_IY:
+			case DCH_Y:
+			case DCH_I:
+			case DCH_RM:
+			case DCH_rm:
+			case DCH_W:
+			case DCH_J:
+				flags |= DCH_DATED;
+				break;
+		}
+	}
+
+	return flags;
+}
+
 /* select a DCHCacheEntry to hold the given format picture */
 static DCHCacheEntry *
 DCH_cache_getnew(const char *str)
@@ -3688,8 +3865,9 @@ to_timestamp(PG_FUNCTION_ARGS)
 	int			tz;
 	struct pg_tm tm;
 	fsec_t		fsec;
+	int			fprec;
 
-	do_to_timestamp(date_txt, fmt, &tm, &fsec);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL);
 
 	/* Use the specified time zone, if any. */
 	if (tm.tm_zone)
@@ -3707,6 +3885,10 @@ to_timestamp(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 				 errmsg("timestamp out of range")));
 
+	/* Use the specified fractional precision, if any. */
+	if (fprec)
+		AdjustTimestampForTypmod(&result, fprec);
+
 	PG_RETURN_TIMESTAMP(result);
 }
 
@@ -3724,7 +3906,7 @@ to_date(PG_FUNCTION_ARGS)
 	struct pg_tm tm;
 	fsec_t		fsec;
 
-	do_to_timestamp(date_txt, fmt, &tm, &fsec);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL);
 
 	/* Prevent overflow in Julian-day routines */
 	if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3746,10 +3928,168 @@ to_date(PG_FUNCTION_ARGS)
 }
 
 /*
+ * Convert the 'date_txt' input to a datetime type using argument 'fmt' as a format string.
+ * The actual data type (returned in 'typid', 'typmod') is determined by
+ * the presence of date/time/zone components in the format string.
+ *
+ * When timezone component is present, the corresponding offset is set to '*tz'.
+ */
+Datum
+parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
+			   int32 *typmod, int *tz)
+{
+	struct pg_tm tm;
+	fsec_t		fsec;
+	int			fprec = 0;
+	int			flags;
+
+	do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags);
+
+	*typmod = fprec ? fprec : -1;	/* fractional part precision */
+
+	if (flags & DCH_DATED)
+	{
+		if (flags & DCH_TIMED)
+		{
+			if (flags & DCH_ZONED)
+			{
+				TimestampTz result;
+
+				if (tm.tm_zone)
+				{
+					int			dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
+
+					if (dterr)
+						DateTimeParseError(dterr, text_to_cstring(date_txt), "timestamptz");
+				}
+				else
+				{
+					if (*tz == PG_INT32_MIN)
+						ereport(ERROR,
+								(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								 errmsg("missing time zone in input string for type timestamptz")));
+				}
+
+				if (tm2timestamp(&tm, fsec, tz, &result) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("timestamptz out of range")));
+
+				AdjustTimestampForTypmod(&result, *typmod);
+
+				*typid = TIMESTAMPTZOID;
+				return TimestampTzGetDatum(result);
+			}
+			else
+			{
+				Timestamp	result;
+
+				if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("timestamp out of range")));
+
+				AdjustTimestampForTypmod(&result, *typmod);
+
+				*typid = TIMESTAMPOID;
+				return TimestampGetDatum(result);
+			}
+		}
+		else
+		{
+			if (flags & DCH_ZONED)
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+						 errmsg("datetime format is zoned but not timed")));
+			}
+			else
+			{
+				DateADT		result;
+
+				/* Prevent overflow in Julian-day routines */
+				if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("date out of range: \"%s\"",
+									text_to_cstring(date_txt))));
+
+				result = 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(result))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("date out of range: \"%s\"",
+									text_to_cstring(date_txt))));
+
+				*typid = DATEOID;
+				return DateADTGetDatum(result);
+			}
+		}
+	}
+	else if (flags & DCH_TIMED)
+	{
+		if (flags & DCH_ZONED)
+		{
+			TimeTzADT  *result = palloc(sizeof(TimeTzADT));
+
+			if (tm.tm_zone)
+			{
+				int			dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
+
+				if (dterr)
+					DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz");
+			}
+			else
+			{
+				if (*tz == PG_INT32_MIN)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							 errmsg("missing time zone in input string for type timetz")));
+			}
+
+			if (tm2timetz(&tm, fsec, *tz, result) != 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("timetz out of range")));
+
+			AdjustTimeForTypmod(&result->time, *typmod);
+
+			*typid = TIMETZOID;
+			return TimeTzADTPGetDatum(result);
+		}
+		else
+		{
+			TimeADT		result;
+
+			if (tm2time(&tm, fsec, &result) != 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("time out of range")));
+
+			AdjustTimeForTypmod(&result, *typmod);
+
+			*typid = TIMEOID;
+			return TimeADTGetDatum(result);
+		}
+	}
+	else
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+				 errmsg("datetime format is not dated and not timed")));
+	}
+
+	return (Datum) 0;
+}
+
+/*
  * do_to_timestamp: shared code for to_timestamp and to_date
  *
- * Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm
- * and fractional seconds.
+ * Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm,
+ * fractional seconds, and fractional precision.
  *
  * We parse 'fmt' into a list of FormatNodes, which is then passed to
  * DCH_from_char to populate a TmFromChar with the parsed contents of
@@ -3757,10 +4097,15 @@ to_date(PG_FUNCTION_ARGS)
  *
  * The TmFromChar is then analysed and converted into the final results in
  * struct 'tm' and 'fsec'.
+ *
+ * Bit mask of date/time/zone components found in 'fmt' is returned in 'flags'.
+ *
+ * 'strict' enables error reporting on unmatched trailing characters in input
+ * or format string patterns.
  */
 static void
-do_to_timestamp(text *date_txt, text *fmt,
-				struct pg_tm *tm, fsec_t *fsec)
+do_to_timestamp(text *date_txt, text *fmt, bool strict,
+				struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags)
 {
 	FormatNode *format;
 	TmFromChar	tmfc;
@@ -3813,9 +4158,13 @@ do_to_timestamp(text *date_txt, text *fmt,
 		/* dump_index(DCH_keywords, DCH_index); */
 #endif
 
-		DCH_from_char(format, date_str, &tmfc);
+		DCH_from_char(format, date_str, &tmfc, strict);
 
 		pfree(fmt_str);
+
+		if (flags)
+			*flags = DCH_datetime_type(format);
+
 		if (!incache)
 			pfree(format);
 	}
@@ -3997,6 +4346,8 @@ do_to_timestamp(text *date_txt, text *fmt,
 		*fsec += tmfc.ms * 1000;
 	if (tmfc.us)
 		*fsec += tmfc.us;
+	if (fprec)
+		*fprec = tmfc.ff;		/* fractional precision, if specified */
 
 	/* Range-check date fields according to bit mask computed above */
 	if (fmask != 0)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 5861ffb..2931bd5 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -70,7 +70,6 @@ typedef struct
 
 static TimeOffset time2t(const int hour, const int min, const int sec, const fsec_t fsec);
 static Timestamp dt2local(Timestamp dt, int timezone);
-static void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
 static void AdjustIntervalForTypmod(Interval *interval, int32 typmod);
 static TimestampTz timestamp2timestamptz(Timestamp timestamp);
 static Timestamp timestamptz2timestamp(TimestampTz timestamp);
@@ -333,7 +332,7 @@ timestamp_scale(PG_FUNCTION_ARGS)
  * AdjustTimestampForTypmod --- round off a timestamp to suit given typmod
  * Works for either timestamp or timestamptz.
  */
-static void
+void
 AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
 {
 	static const int64 TimestampScales[MAX_TIMESTAMP_PRECISION + 1] = {
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index bec129a..bd15bfa 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -76,5 +76,8 @@ extern TimeTzADT *GetSQLCurrentTime(int32 typmod);
 extern TimeADT GetSQLLocalTime(int32 typmod);
 extern int	time2tm(TimeADT time, struct pg_tm *tm, fsec_t *fsec);
 extern int	timetz2tm(TimeTzADT *time, struct pg_tm *tm, fsec_t *fsec, int *tzp);
+extern int	tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
+extern int	tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
+extern void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
 
 #endif							/* DATE_H */
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index b8a199c..0cafdd2 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -336,4 +336,6 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
 												   int n);
 extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
 
+extern void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
+
 #endif							/* DATETIME_H */
diff --git a/src/include/utils/formatting.h b/src/include/utils/formatting.h
index 5b275dc..a390302 100644
--- a/src/include/utils/formatting.h
+++ b/src/include/utils/formatting.h
@@ -28,4 +28,7 @@ extern char *asc_tolower(const char *buff, size_t nbytes);
 extern char *asc_toupper(const char *buff, size_t nbytes);
 extern char *asc_initcap(const char *buff, size_t nbytes);
 
+extern Datum parse_datetime(text *date_txt, text *fmt, bool strict,
+							Oid *typid, int32 *typmod, int *tz);
+
 #endif
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index b2b4577..74ecb7c 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -2786,6 +2786,85 @@ SELECT to_timestamp('2011-12-18 11:38 20',     'YYYY-MM-DD HH12:MI TZM');
  Sun Dec 18 03:18:00 2011 PST
 (1 row)
 
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |         to_timestamp         
+---+------------------------------
+ 1 | Fri Nov 02 12:34:56 2018 PDT
+ 2 | Fri Nov 02 12:34:56 2018 PDT
+ 3 | Fri Nov 02 12:34:56 2018 PDT
+ 4 | Fri Nov 02 12:34:56 2018 PDT
+ 5 | Fri Nov 02 12:34:56 2018 PDT
+ 6 | Fri Nov 02 12:34:56 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |          to_timestamp          
+---+--------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.1 2018 PDT
+ 3 | Fri Nov 02 12:34:56.1 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |          to_timestamp           
+---+---------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.12 2018 PDT
+ 4 | Fri Nov 02 12:34:56.12 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |           to_timestamp           
+---+----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.123 2018 PDT
+ 5 | Fri Nov 02 12:34:56.123 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |           to_timestamp            
+---+-----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1234 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |            to_timestamp            
+---+------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12345 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12345 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |            to_timestamp             
+---+-------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12346 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123456 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ERROR:  date/time field value out of range: "2018-11-02 12:34:56.123456789"
 --
 -- Check handling of multiple spaces in format and/or input
 --
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index 715680e..f772b07 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -1584,6 +1584,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
             | 2001 1 1 1 1 1 1
 (65 rows)
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamp),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+ to_char_12 |                              to_char                               
+------------+--------------------------------------------------------------------
+            | 0 00 000 0000 00000 000000  0 00 000 0000 00000 000000  000 000000
+            | 7 78 780 7800 78000 780000  7 78 780 7800 78000 780000  780 780000
+            | 7 78 789 7890 78901 789010  7 78 789 7890 78901 789010  789 789010
+            | 7 78 789 7890 78901 789012  7 78 789 7890 78901 789012  789 789012
+(4 rows)
+
 -- timestamp numeric fields constructor
 SELECT make_timestamp(2014,12,28,6,30,45.887);
         make_timestamp        
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 5551fa6..2d6a71c 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -1704,6 +1704,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
             | 2001 1 1 1 1 1 1
 (66 rows)
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamptz),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+ to_char_12 |                              to_char                               
+------------+--------------------------------------------------------------------
+            | 0 00 000 0000 00000 000000  0 00 000 0000 00000 000000  000 000000
+            | 7 78 780 7800 78000 780000  7 78 780 7800 78000 780000  780 780000
+            | 7 78 789 7890 78901 789010  7 78 789 7890 78901 789010  789 789010
+            | 7 78 789 7890 78901 789012  7 78 789 7890 78901 789012  789 789012
+(4 rows)
+
 -- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
 SET timezone = '00:00';
 SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index e356dd5..3c85803 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -402,6 +402,15 @@ SELECT to_timestamp('2011-12-18 11:38 +05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
 SELECT to_timestamp('2011-12-18 11:38 -05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
 SELECT to_timestamp('2011-12-18 11:38 20',     'YYYY-MM-DD HH12:MI TZM');
 
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+
 --
 -- Check handling of multiple spaces in format and/or input
 --
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index 031b22b..329987f 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -224,5 +224,13 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
 SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
    FROM TIMESTAMP_TBL;
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamp),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+
 -- timestamp numeric fields constructor
 SELECT make_timestamp(2014,12,28,6,30,45.887);
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index 28c76d6..f5fee63 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -248,6 +248,14 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
 SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
    FROM TIMESTAMPTZ_TBL;
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamptz),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+
 -- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
 SET timezone = '00:00';
 SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
0001-datetime-in-JsonbValue-5.patchtext/x-patch; name=0001-datetime-in-JsonbValue-5.patchDownload
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index 26d2937..be87f88 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -1506,7 +1506,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 			{
 				char		buf[MAXDATELEN + 1];
 
-				JsonEncodeDateTime(buf, val, DATEOID);
+				JsonEncodeDateTime(buf, val, DATEOID, NULL);
 				appendStringInfo(result, "\"%s\"", buf);
 			}
 			break;
@@ -1514,7 +1514,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 			{
 				char		buf[MAXDATELEN + 1];
 
-				JsonEncodeDateTime(buf, val, TIMESTAMPOID);
+				JsonEncodeDateTime(buf, val, TIMESTAMPOID, NULL);
 				appendStringInfo(result, "\"%s\"", buf);
 			}
 			break;
@@ -1522,7 +1522,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 			{
 				char		buf[MAXDATELEN + 1];
 
-				JsonEncodeDateTime(buf, val, TIMESTAMPTZOID);
+				JsonEncodeDateTime(buf, val, TIMESTAMPTZOID, NULL);
 				appendStringInfo(result, "\"%s\"", buf);
 			}
 			break;
@@ -1550,10 +1550,11 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 
 /*
  * Encode 'value' of datetime type 'typid' into JSON string in ISO format using
- * optionally preallocated buffer 'buf'.
+ * optionally preallocated buffer 'buf'.  Optional 'tzp' determines time-zone
+ * offset (in seconds) in which we want to show timestamptz.
  */
 char *
-JsonEncodeDateTime(char *buf, Datum value, Oid typid)
+JsonEncodeDateTime(char *buf, Datum value, Oid typid, const int *tzp)
 {
 	if (!buf)
 		buf = palloc(MAXDATELEN + 1);
@@ -1630,11 +1631,30 @@ JsonEncodeDateTime(char *buf, Datum value, Oid typid)
 				const char *tzn = NULL;
 
 				timestamp = DatumGetTimestampTz(value);
+
+				/*
+				 * If a time zone is specified, we apply the time-zone shift,
+				 * convert timestamptz to pg_tm as if it were without a
+				 * time zone, and then use the specified time zone for converting
+				 * the timestamp into a string.
+				 */
+				if (tzp)
+				{
+					tz = *tzp;
+					timestamp -= (TimestampTz) tz * USECS_PER_SEC;
+				}
+
 				/* Same as timestamptz_out(), but forcing DateStyle */
 				if (TIMESTAMP_NOT_FINITE(timestamp))
 					EncodeSpecialTimestamp(timestamp, buf);
-				else if (timestamp2tm(timestamp, &tz, &tm, &fsec, &tzn, NULL) == 0)
+				else if (timestamp2tm(timestamp, tzp ? NULL : &tz, &tm, &fsec,
+									  tzp ? NULL : &tzn, NULL) == 0)
+				{
+					if (tzp)
+						tm.tm_isdst = 1;	/* set time-zone presence flag */
+
 					EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf);
+				}
 				else
 					ereport(ERROR,
 							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 69f41ab..74b4bbe 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -206,6 +206,24 @@ JsonbTypeName(JsonbValue *jbv)
 			return "boolean";
 		case jbvNull:
 			return "null";
+		case jbvDatetime:
+			switch (jbv->val.datetime.typid)
+			{
+				case DATEOID:
+					return "date";
+				case TIMEOID:
+					return "time without time zone";
+				case TIMETZOID:
+					return "time with time zone";
+				case TIMESTAMPOID:
+					return "timestamp without time zone";
+				case TIMESTAMPTZOID:
+					return "timestamp with time zone";
+				default:
+					elog(ERROR, "unrecognized jsonb value datetime type: %d",
+						 jbv->val.datetime.typid);
+			}
+			return "unknown";
 		default:
 			elog(ERROR, "unrecognized jsonb value type: %d", jbv->type);
 			return "unknown";
@@ -805,17 +823,20 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result,
 				break;
 			case JSONBTYPE_DATE:
 				jb.type = jbvString;
-				jb.val.string.val = JsonEncodeDateTime(NULL, val, DATEOID);
+				jb.val.string.val = JsonEncodeDateTime(NULL, val,
+													   DATEOID, NULL);
 				jb.val.string.len = strlen(jb.val.string.val);
 				break;
 			case JSONBTYPE_TIMESTAMP:
 				jb.type = jbvString;
-				jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPOID);
+				jb.val.string.val = JsonEncodeDateTime(NULL, val,
+													   TIMESTAMPOID, NULL);
 				jb.val.string.len = strlen(jb.val.string.val);
 				break;
 			case JSONBTYPE_TIMESTAMPTZ:
 				jb.type = jbvString;
-				jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPTZOID);
+				jb.val.string.val = JsonEncodeDateTime(NULL, val,
+													   TIMESTAMPTZOID, NULL);
 				jb.val.string.len = strlen(jb.val.string.val);
 				break;
 			case JSONBTYPE_JSONCAST:
diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index 7969f6f..c549673 100644
--- a/src/backend/utils/adt/jsonb_util.c
+++ b/src/backend/utils/adt/jsonb_util.c
@@ -14,9 +14,12 @@
 #include "postgres.h"
 
 #include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
 #include "miscadmin.h"
 #include "utils/builtins.h"
+#include "utils/datetime.h"
 #include "utils/hashutils.h"
+#include "utils/jsonapi.h"
 #include "utils/jsonb.h"
 #include "utils/memutils.h"
 #include "utils/varlena.h"
@@ -242,6 +245,8 @@ compareJsonbContainers(JsonbContainer *a, JsonbContainer *b)
 						break;
 					case jbvBinary:
 						elog(ERROR, "unexpected jbvBinary value");
+					case jbvDatetime:
+						elog(ERROR, "unexpected jbvDatetime value");
 				}
 			}
 			else
@@ -1749,6 +1754,22 @@ convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal)
 				JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE;
 			break;
 
+		case jbvDatetime:
+			{
+				char		buf[MAXDATELEN + 1];
+				size_t		len;
+
+				JsonEncodeDateTime(buf,
+								   scalarVal->val.datetime.value,
+								   scalarVal->val.datetime.typid,
+								   &scalarVal->val.datetime.tz);
+				len = strlen(buf);
+				appendToBuffer(buffer, buf, len);
+
+				*jentry = len;
+			}
+			break;
+
 		default:
 			elog(ERROR, "invalid jsonb scalar type");
 	}
diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h
index 5f4d479..b707b09 100644
--- a/src/include/utils/jsonapi.h
+++ b/src/include/utils/jsonapi.h
@@ -161,6 +161,7 @@ extern Jsonb *transform_jsonb_string_values(Jsonb *jsonb, void *action_state,
 extern text *transform_json_string_values(text *json, void *action_state,
 										  JsonTransformStringValuesAction transform_action);
 
-extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid);
+extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid,
+								const int *tzp);
 
 #endif							/* JSONAPI_H */
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index 2fe7d32..d96ba31 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -241,7 +241,15 @@ enum jbvType
 	jbvArray = 0x10,
 	jbvObject,
 	/* Binary (i.e. struct Jsonb) jbvArray/jbvObject */
-	jbvBinary
+	jbvBinary,
+
+	/*
+	 * Virtual types.
+	 *
+	 * These types are used only for in-memory JSON processing and serialized
+	 * into JSON strings when outputted to json/jsonb.
+	 */
+	jbvDatetime = 0x20,
 };
 
 /*
@@ -282,11 +290,21 @@ struct JsonbValue
 			int			len;
 			JsonbContainer *data;
 		}			binary;		/* Array or object, in on-disk format */
+
+		struct
+		{
+			Datum		value;
+			Oid			typid;
+			int32		typmod;
+			int			tz;		/* Numeric time zone, in seconds, for
+								 * TimestampTz data type */
+		}			datetime;
 	}			val;
 };
 
-#define IsAJsonbScalar(jsonbval)	((jsonbval)->type >= jbvNull && \
-									 (jsonbval)->type <= jbvBool)
+#define IsAJsonbScalar(jsonbval)	(((jsonbval)->type >= jbvNull && \
+									  (jsonbval)->type <= jbvBool) || \
+									  (jsonbval)->type == jbvDatetime)
 
 /*
  * Key/value pair within an Object.
#7Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Liudmila Mantrova (#6)
4 attachment(s)
Re: Support for jsonpath .datetime() method

Hi, Liudmila!

On Fri, Jul 19, 2019 at 5:30 PM Liudmila Mantrova
<l.mantrova@postgrespro.ru> wrote:

I had look at the added docs and would like to suggest a couple of
changes. Please see the attached patches with my my edits for func.sgml
and some of the comments.

Thank you for your edits, they look good to me. Attached patchset
contains your edits as well as revised commit messages.

Looks like we also need to change the following entry in
features-unsupported.sgml, and probably move it to features-supported.sgml?

<row>
<entry>T832</entry>
<entry></entry>
<entry>SQL/JSON path language: item method</entry>
<entry>datetime() not yet implemented</entry>
</row>

Yes, that's it. Attached patch updates sql_features.txt, which is a
source for generation of both features-unsupported.sgml and
features-supported.sgml.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0001-datetime-in-JsonbValue-6.patchapplication/octet-stream; name=0001-datetime-in-JsonbValue-6.patchDownload
commit cbd65412d7fefc449ef2d0f32ccfd41bc53e9833
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Sat Jul 20 21:50:48 2019 +0300

    Allow datetime values in JsonbValue
    
    SQL/JSON standard allows manipulation with datetime values.  So, it appears to
    be convinient to allow datetime values to be represented in JsonbValue struct.
    These datetime values are allowed for temporary representation only.  During
    serialization datetime values are converted into strings.
    
    SQL/JSON requires writing timestamps with timezone in the same timezone offset
    as they were parsed.  This is why we allow storage of timezone offset in
    JsonbValue struct.  For the same reason timezone offset argument is added to
    JsonEncodeDateTime() function.
    
    Extracted from original patch by Nikita Glukhov, Teodor Sigaev, Oleg Bartunov.
    Revised by me.  Comments were adjusted by Liudmila Mantrova.
    
    Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
    Discussion: https://postgr.es/m/CAPpHfdsZgYEra_PeCLGNoXOWYx6iU-S3wF8aX0ObQUcZU%2B4XTw%40mail.gmail.com
    Author: Nikita Glukhov, Teodor Sigaev, Oleg Bartunov, Alexander Korotkov, Liudmila Mantrova
    Reviewed-by: Anastasia Lubennikova

diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index 26d293709aa..d4ba3bd87db 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -1506,7 +1506,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 			{
 				char		buf[MAXDATELEN + 1];
 
-				JsonEncodeDateTime(buf, val, DATEOID);
+				JsonEncodeDateTime(buf, val, DATEOID, NULL);
 				appendStringInfo(result, "\"%s\"", buf);
 			}
 			break;
@@ -1514,7 +1514,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 			{
 				char		buf[MAXDATELEN + 1];
 
-				JsonEncodeDateTime(buf, val, TIMESTAMPOID);
+				JsonEncodeDateTime(buf, val, TIMESTAMPOID, NULL);
 				appendStringInfo(result, "\"%s\"", buf);
 			}
 			break;
@@ -1522,7 +1522,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 			{
 				char		buf[MAXDATELEN + 1];
 
-				JsonEncodeDateTime(buf, val, TIMESTAMPTZOID);
+				JsonEncodeDateTime(buf, val, TIMESTAMPTZOID, NULL);
 				appendStringInfo(result, "\"%s\"", buf);
 			}
 			break;
@@ -1550,10 +1550,11 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 
 /*
  * Encode 'value' of datetime type 'typid' into JSON string in ISO format using
- * optionally preallocated buffer 'buf'.
+ * optionally preallocated buffer 'buf'.  Optional 'tzp' determines time-zone
+ * offset (in seconds) in which we want to show timestamptz.
  */
 char *
-JsonEncodeDateTime(char *buf, Datum value, Oid typid)
+JsonEncodeDateTime(char *buf, Datum value, Oid typid, const int *tzp)
 {
 	if (!buf)
 		buf = palloc(MAXDATELEN + 1);
@@ -1630,11 +1631,30 @@ JsonEncodeDateTime(char *buf, Datum value, Oid typid)
 				const char *tzn = NULL;
 
 				timestamp = DatumGetTimestampTz(value);
+
+				/*
+				 * If a time zone is specified, we apply the time-zone shift,
+				 * convert timestamptz to pg_tm as if it were without a time
+				 * zone, and then use the specified time zone for converting
+				 * the timestamp into a string.
+				 */
+				if (tzp)
+				{
+					tz = *tzp;
+					timestamp -= (TimestampTz) tz * USECS_PER_SEC;
+				}
+
 				/* Same as timestamptz_out(), but forcing DateStyle */
 				if (TIMESTAMP_NOT_FINITE(timestamp))
 					EncodeSpecialTimestamp(timestamp, buf);
-				else if (timestamp2tm(timestamp, &tz, &tm, &fsec, &tzn, NULL) == 0)
+				else if (timestamp2tm(timestamp, tzp ? NULL : &tz, &tm, &fsec,
+									  tzp ? NULL : &tzn, NULL) == 0)
+				{
+					if (tzp)
+						tm.tm_isdst = 1;	/* set time-zone presence flag */
+
 					EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf);
+				}
 				else
 					ereport(ERROR,
 							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 69f41ab4556..74b4bbe44c6 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -206,6 +206,24 @@ JsonbTypeName(JsonbValue *jbv)
 			return "boolean";
 		case jbvNull:
 			return "null";
+		case jbvDatetime:
+			switch (jbv->val.datetime.typid)
+			{
+				case DATEOID:
+					return "date";
+				case TIMEOID:
+					return "time without time zone";
+				case TIMETZOID:
+					return "time with time zone";
+				case TIMESTAMPOID:
+					return "timestamp without time zone";
+				case TIMESTAMPTZOID:
+					return "timestamp with time zone";
+				default:
+					elog(ERROR, "unrecognized jsonb value datetime type: %d",
+						 jbv->val.datetime.typid);
+			}
+			return "unknown";
 		default:
 			elog(ERROR, "unrecognized jsonb value type: %d", jbv->type);
 			return "unknown";
@@ -805,17 +823,20 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result,
 				break;
 			case JSONBTYPE_DATE:
 				jb.type = jbvString;
-				jb.val.string.val = JsonEncodeDateTime(NULL, val, DATEOID);
+				jb.val.string.val = JsonEncodeDateTime(NULL, val,
+													   DATEOID, NULL);
 				jb.val.string.len = strlen(jb.val.string.val);
 				break;
 			case JSONBTYPE_TIMESTAMP:
 				jb.type = jbvString;
-				jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPOID);
+				jb.val.string.val = JsonEncodeDateTime(NULL, val,
+													   TIMESTAMPOID, NULL);
 				jb.val.string.len = strlen(jb.val.string.val);
 				break;
 			case JSONBTYPE_TIMESTAMPTZ:
 				jb.type = jbvString;
-				jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPTZOID);
+				jb.val.string.val = JsonEncodeDateTime(NULL, val,
+													   TIMESTAMPTZOID, NULL);
 				jb.val.string.len = strlen(jb.val.string.val);
 				break;
 			case JSONBTYPE_JSONCAST:
diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index 7969f6f5843..c5496733a10 100644
--- a/src/backend/utils/adt/jsonb_util.c
+++ b/src/backend/utils/adt/jsonb_util.c
@@ -14,9 +14,12 @@
 #include "postgres.h"
 
 #include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
 #include "miscadmin.h"
 #include "utils/builtins.h"
+#include "utils/datetime.h"
 #include "utils/hashutils.h"
+#include "utils/jsonapi.h"
 #include "utils/jsonb.h"
 #include "utils/memutils.h"
 #include "utils/varlena.h"
@@ -242,6 +245,8 @@ compareJsonbContainers(JsonbContainer *a, JsonbContainer *b)
 						break;
 					case jbvBinary:
 						elog(ERROR, "unexpected jbvBinary value");
+					case jbvDatetime:
+						elog(ERROR, "unexpected jbvDatetime value");
 				}
 			}
 			else
@@ -1749,6 +1754,22 @@ convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal)
 				JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE;
 			break;
 
+		case jbvDatetime:
+			{
+				char		buf[MAXDATELEN + 1];
+				size_t		len;
+
+				JsonEncodeDateTime(buf,
+								   scalarVal->val.datetime.value,
+								   scalarVal->val.datetime.typid,
+								   &scalarVal->val.datetime.tz);
+				len = strlen(buf);
+				appendToBuffer(buffer, buf, len);
+
+				*jentry = len;
+			}
+			break;
+
 		default:
 			elog(ERROR, "invalid jsonb scalar type");
 	}
diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h
index 5f4d479a7b2..b707b09ce31 100644
--- a/src/include/utils/jsonapi.h
+++ b/src/include/utils/jsonapi.h
@@ -161,6 +161,7 @@ extern Jsonb *transform_jsonb_string_values(Jsonb *jsonb, void *action_state,
 extern text *transform_json_string_values(text *json, void *action_state,
 										  JsonTransformStringValuesAction transform_action);
 
-extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid);
+extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid,
+								const int *tzp);
 
 #endif							/* JSONAPI_H */
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index 2fe7d32fec2..d96ba31fcf1 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -241,7 +241,15 @@ enum jbvType
 	jbvArray = 0x10,
 	jbvObject,
 	/* Binary (i.e. struct Jsonb) jbvArray/jbvObject */
-	jbvBinary
+	jbvBinary,
+
+	/*
+	 * Virtual types.
+	 *
+	 * These types are used only for in-memory JSON processing and serialized
+	 * into JSON strings when outputted to json/jsonb.
+	 */
+	jbvDatetime = 0x20,
 };
 
 /*
@@ -282,11 +290,21 @@ struct JsonbValue
 			int			len;
 			JsonbContainer *data;
 		}			binary;		/* Array or object, in on-disk format */
+
+		struct
+		{
+			Datum		value;
+			Oid			typid;
+			int32		typmod;
+			int			tz;		/* Numeric time zone, in seconds, for
+								 * TimestampTz data type */
+		}			datetime;
 	}			val;
 };
 
-#define IsAJsonbScalar(jsonbval)	((jsonbval)->type >= jbvNull && \
-									 (jsonbval)->type <= jbvBool)
+#define IsAJsonbScalar(jsonbval)	(((jsonbval)->type >= jbvNull && \
+									  (jsonbval)->type <= jbvBool) || \
+									  (jsonbval)->type == jbvDatetime)
 
 /*
  * Key/value pair within an Object.
0002-datetime-conversion-for-jsonpath-6.patchapplication/octet-stream; name=0002-datetime-conversion-for-jsonpath-6.patchDownload
commit 71ee8a47650031beb2d4f9526eb330107f2556dd
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Sun Jul 21 01:12:16 2019 +0300

    Improve datetime conversion for upcoming jsonpath .datetime() method
    
    Jsonpath language (part of SQL/JSON standard) includes functions for datetime
    conversion.  In order to support that, we have to extend our infrastructure
    in following ways.
    
      1. FF1-FF6 format patterns implementing different fractions of second.  FF3
         and FF6 are effectively just synonyms for MS and US.  But other fractions
         were not implemented yet.
      2. to_datetime() internal function, which dynamically determines result
         datatype depending on format string.
      3. Strict parsing mode, which doesn't allow trailing spaces and unmatched
         format patterns.
    
    The first improvement is already user-visible and can use used in datetime
    parsing/printing functions.  Other improvements are internal, they will be
    user-visible together with jsonpath.
    
    Extracted from original patch by Nikita Glukhov, Teodor Sigaev, Oleg Bartunov.
    Revised by me.  Comments were adjusted by Liudmila Mantrova.
    
    Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
    Discussion: https://postgr.es/m/CAPpHfdsZgYEra_PeCLGNoXOWYx6iU-S3wF8aX0ObQUcZU%2B4XTw%40mail.gmail.com
    Author: Nikita Glukhov, Teodor Sigaev, Oleg Bartunov, Alexander Korotkov, Liudmila Mantrova
    Reviewed-by: Anastasia Lubennikova

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index c2f5a75ff67..afde81664bb 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -6122,6 +6122,30 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
         <entry><literal>US</literal></entry>
         <entry>microsecond (000000-999999)</entry>
        </row>
+       <row>
+        <entry><literal>FF1</literal></entry>
+        <entry>decisecond (0-9)</entry>
+       </row>
+       <row>
+        <entry><literal>FF2</literal></entry>
+        <entry>centisecond (00-99)</entry>
+       </row>
+       <row>
+        <entry><literal>FF3</literal></entry>
+        <entry>millisecond (000-999)</entry>
+       </row>
+       <row>
+        <entry><literal>FF4</literal></entry>
+        <entry>tenth of a millisecond (0000-9999)</entry>
+       </row>
+       <row>
+        <entry><literal>FF5</literal></entry>
+        <entry>hundredth of a millisecond (00000-99999)</entry>
+       </row>
+       <row>
+        <entry><literal>FF6</literal></entry>
+        <entry>microsecond (000000-999999)</entry>
+       </row>
        <row>
         <entry><literal>SSSS</literal></entry>
         <entry>seconds past midnight (0-86399)</entry>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 4b1afb10f92..9e291b5c7bc 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -41,11 +41,6 @@
 #endif
 
 
-static int	tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
-static int	tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
-static void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
-
-
 /* common code for timetypmodin and timetztypmodin */
 static int32
 anytime_typmodin(bool istz, ArrayType *ta)
@@ -1203,7 +1198,7 @@ time_in(PG_FUNCTION_ARGS)
 /* tm2time()
  * Convert a tm structure to a time data type.
  */
-static int
+int
 tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result)
 {
 	*result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec)
@@ -1379,7 +1374,7 @@ time_scale(PG_FUNCTION_ARGS)
  * have a fundamental tie together but rather a coincidence of
  * implementation. - thomas
  */
-static void
+void
 AdjustTimeForTypmod(TimeADT *time, int32 typmod)
 {
 	static const int64 TimeScales[MAX_TIME_PRECISION + 1] = {
@@ -1957,7 +1952,7 @@ time_part(PG_FUNCTION_ARGS)
 /* tm2timetz()
  * Convert a tm structure to a time data type.
  */
-static int
+int
 tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result)
 {
 	result->time = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) *
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index b3115e4bea8..d06c778d2d2 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -86,6 +86,7 @@
 #endif
 
 #include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
 #include "mb/pg_wchar.h"
 #include "utils/builtins.h"
 #include "utils/date.h"
@@ -434,7 +435,8 @@ typedef struct
 				clock,			/* 12 or 24 hour clock? */
 				tzsign,			/* +1, -1 or 0 if timezone info is absent */
 				tzh,
-				tzm;
+				tzm,
+				ff;				/* fractional precision */
 } TmFromChar;
 
 #define ZERO_tmfc(_X) memset(_X, 0, sizeof(TmFromChar))
@@ -594,6 +596,12 @@ typedef enum
 	DCH_Day,
 	DCH_Dy,
 	DCH_D,
+	DCH_FF1,
+	DCH_FF2,
+	DCH_FF3,
+	DCH_FF4,
+	DCH_FF5,
+	DCH_FF6,
 	DCH_FX,						/* global suffix */
 	DCH_HH24,
 	DCH_HH12,
@@ -643,6 +651,12 @@ typedef enum
 	DCH_dd,
 	DCH_dy,
 	DCH_d,
+	DCH_ff1,
+	DCH_ff2,
+	DCH_ff3,
+	DCH_ff4,
+	DCH_ff5,
+	DCH_ff6,
 	DCH_fx,
 	DCH_hh24,
 	DCH_hh12,
@@ -743,7 +757,13 @@ static const KeyWord DCH_keywords[] = {
 	{"Day", 3, DCH_Day, false, FROM_CHAR_DATE_NONE},
 	{"Dy", 2, DCH_Dy, false, FROM_CHAR_DATE_NONE},
 	{"D", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
-	{"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},	/* F */
+	{"FF1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE},	/* F */
+	{"FF2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE},
+	{"FF3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE},
+	{"FF4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE},
+	{"FF5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE},
+	{"FF6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE},
+	{"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
 	{"HH24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE},	/* H */
 	{"HH12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
 	{"HH", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
@@ -792,7 +812,13 @@ static const KeyWord DCH_keywords[] = {
 	{"dd", 2, DCH_DD, true, FROM_CHAR_DATE_GREGORIAN},
 	{"dy", 2, DCH_dy, false, FROM_CHAR_DATE_NONE},
 	{"d", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
-	{"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},	/* f */
+	{"ff1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE},	/* f */
+	{"ff2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE},
+	{"ff3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE},
+	{"ff4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE},
+	{"ff5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE},
+	{"ff6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE},
+	{"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
 	{"hh24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE},	/* h */
 	{"hh12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
 	{"hh", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
@@ -893,10 +919,10 @@ static const int DCH_index[KeyWord_INDEX_SIZE] = {
 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 	-1, -1, -1, -1, -1, DCH_A_D, DCH_B_C, DCH_CC, DCH_DAY, -1,
-	DCH_FX, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
+	DCH_FF1, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
 	DCH_P_M, DCH_Q, DCH_RM, DCH_SSSS, DCH_TZH, DCH_US, -1, DCH_WW, -1, DCH_Y_YYY,
 	-1, -1, -1, -1, -1, -1, -1, DCH_a_d, DCH_b_c, DCH_cc,
-	DCH_day, -1, DCH_fx, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
+	DCH_day, -1, DCH_ff1, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
 	-1, -1, DCH_p_m, DCH_q, DCH_rm, DCH_ssss, DCH_tz, DCH_us, -1, DCH_ww,
 	-1, DCH_y_yyy, -1, -1, -1, -1
 
@@ -960,6 +986,10 @@ typedef struct NUMProc
 			   *L_currency_symbol;
 } NUMProc;
 
+/* Return flags for DCH_from_char() */
+#define DCH_DATED	0x01
+#define DCH_TIMED	0x02
+#define DCH_ZONED	0x04
 
 /* ----------
  * Functions
@@ -975,7 +1005,8 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 
 static void DCH_to_char(FormatNode *node, bool is_interval,
 						TmToChar *in, char *out, Oid collid);
-static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out);
+static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
+						  bool strict);
 
 #ifdef DEBUG_TO_FROM_CHAR
 static void dump_index(const KeyWord *k, const int *index);
@@ -992,8 +1023,8 @@ static int	from_char_parse_int_len(int *dest, char **src, const int len, FormatN
 static int	from_char_parse_int(int *dest, char **src, FormatNode *node);
 static int	seq_search(char *name, const char *const *array, int type, int max, int *len);
 static int	from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
-static void do_to_timestamp(text *date_txt, text *fmt,
-							struct pg_tm *tm, fsec_t *fsec);
+static void do_to_timestamp(text *date_txt, text *fmt, bool strict,
+							struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags);
 static char *fill_str(char *str, int c, int max);
 static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
 static char *int_to_roman(int number);
@@ -2515,18 +2546,32 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 					str_numth(s, s, S_TH_TYPE(n->suffix));
 				s += strlen(s);
 				break;
-			case DCH_MS:		/* millisecond */
-				sprintf(s, "%03d", (int) (in->fsec / INT64CONST(1000)));
-				if (S_THth(n->suffix))
-					str_numth(s, s, S_TH_TYPE(n->suffix));
+#define DCH_to_char_fsec(frac_fmt, frac_val) \
+				sprintf(s, frac_fmt, (int) (frac_val)); \
+				if (S_THth(n->suffix)) \
+					str_numth(s, s, S_TH_TYPE(n->suffix)); \
 				s += strlen(s);
+			case DCH_FF1:		/* decisecond */
+				DCH_to_char_fsec("%01d", in->fsec / 100000);
+				break;
+			case DCH_FF2:		/* centisecond */
+				DCH_to_char_fsec("%02d", in->fsec / 10000);
+				break;
+			case DCH_FF3:
+			case DCH_MS:		/* millisecond */
+				DCH_to_char_fsec("%03d", in->fsec / 1000);
+				break;
+			case DCH_FF4:
+				DCH_to_char_fsec("%04d", in->fsec / 100);
 				break;
+			case DCH_FF5:
+				DCH_to_char_fsec("%05d", in->fsec / 10);
+				break;
+			case DCH_FF6:
 			case DCH_US:		/* microsecond */
-				sprintf(s, "%06d", (int) in->fsec);
-				if (S_THth(n->suffix))
-					str_numth(s, s, S_TH_TYPE(n->suffix));
-				s += strlen(s);
+				DCH_to_char_fsec("%06d", in->fsec);
 				break;
+#undef DCH_to_char_fsec
 			case DCH_SSSS:
 				sprintf(s, "%d", tm->tm_hour * SECS_PER_HOUR +
 						tm->tm_min * SECS_PER_MINUTE +
@@ -3008,13 +3053,15 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 /* ----------
  * Process a string as denoted by a list of FormatNodes.
  * The TmFromChar struct pointed to by 'out' is populated with the results.
+ * 'strict' enables error reporting on unmatched trailing characters in input
+ * or format string patterns.
  *
  * Note: we currently don't have any to_interval() function, so there
  * is no need here for INVALID_FOR_INTERVAL checks.
  * ----------
  */
 static void
-DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
+DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 {
 	FormatNode *n;
 	char	   *s;
@@ -3151,8 +3198,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 
 				SKIP_THth(s, n->suffix);
 				break;
+			case DCH_FF1:
+			case DCH_FF2:
+			case DCH_FF3:
+			case DCH_FF4:
+			case DCH_FF5:
+			case DCH_FF6:
+				out->ff = n->key->id - DCH_FF1 + 1;
+				/* fall through */
 			case DCH_US:		/* microsecond */
-				len = from_char_parse_int_len(&out->us, &s, 6, n);
+				len = from_char_parse_int_len(&out->us, &s,
+											  n->key->id == DCH_US ? 6 :
+											  out->ff, n);
 
 				out->us *= len == 1 ? 100000 :
 					len == 2 ? 10000 :
@@ -3378,6 +3435,23 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 			}
 		}
 	}
+
+	if (strict)
+	{
+		if (n->type != NODE_TYPE_END)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+					 errmsg("input string is too short for datetime format")));
+
+		while (*s != '\0' && isspace((unsigned char) *s))
+			s++;
+
+		if (*s != '\0')
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+					 errmsg("trailing characters remain in input string after "
+							"datetime format")));
+	}
 }
 
 /*
@@ -3398,6 +3472,109 @@ DCH_prevent_counter_overflow(void)
 	}
 }
 
+/* Get mask of date/time/zone components present in format nodes. */
+static int
+DCH_datetime_type(FormatNode *node)
+{
+	FormatNode *n;
+	int			flags = 0;
+
+	for (n = node; n->type != NODE_TYPE_END; n++)
+	{
+		if (n->type != NODE_TYPE_ACTION)
+			continue;
+
+		switch (n->key->id)
+		{
+			case DCH_FX:
+				break;
+			case DCH_A_M:
+			case DCH_P_M:
+			case DCH_a_m:
+			case DCH_p_m:
+			case DCH_AM:
+			case DCH_PM:
+			case DCH_am:
+			case DCH_pm:
+			case DCH_HH:
+			case DCH_HH12:
+			case DCH_HH24:
+			case DCH_MI:
+			case DCH_SS:
+			case DCH_MS:		/* millisecond */
+			case DCH_US:		/* microsecond */
+			case DCH_FF1:
+			case DCH_FF2:
+			case DCH_FF3:
+			case DCH_FF4:
+			case DCH_FF5:
+			case DCH_FF6:
+			case DCH_SSSS:
+				flags |= DCH_TIMED;
+				break;
+			case DCH_tz:
+			case DCH_TZ:
+			case DCH_OF:
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("formatting field \"%s\" is only supported in to_char",
+								n->key->name)));
+				flags |= DCH_ZONED;
+				break;
+			case DCH_TZH:
+			case DCH_TZM:
+				flags |= DCH_ZONED;
+				break;
+			case DCH_A_D:
+			case DCH_B_C:
+			case DCH_a_d:
+			case DCH_b_c:
+			case DCH_AD:
+			case DCH_BC:
+			case DCH_ad:
+			case DCH_bc:
+			case DCH_MONTH:
+			case DCH_Month:
+			case DCH_month:
+			case DCH_MON:
+			case DCH_Mon:
+			case DCH_mon:
+			case DCH_MM:
+			case DCH_DAY:
+			case DCH_Day:
+			case DCH_day:
+			case DCH_DY:
+			case DCH_Dy:
+			case DCH_dy:
+			case DCH_DDD:
+			case DCH_IDDD:
+			case DCH_DD:
+			case DCH_D:
+			case DCH_ID:
+			case DCH_WW:
+			case DCH_Q:
+			case DCH_CC:
+			case DCH_Y_YYY:
+			case DCH_YYYY:
+			case DCH_IYYY:
+			case DCH_YYY:
+			case DCH_IYY:
+			case DCH_YY:
+			case DCH_IY:
+			case DCH_Y:
+			case DCH_I:
+			case DCH_RM:
+			case DCH_rm:
+			case DCH_W:
+			case DCH_J:
+				flags |= DCH_DATED;
+				break;
+		}
+	}
+
+	return flags;
+}
+
 /* select a DCHCacheEntry to hold the given format picture */
 static DCHCacheEntry *
 DCH_cache_getnew(const char *str)
@@ -3686,8 +3863,9 @@ to_timestamp(PG_FUNCTION_ARGS)
 	int			tz;
 	struct pg_tm tm;
 	fsec_t		fsec;
+	int			fprec;
 
-	do_to_timestamp(date_txt, fmt, &tm, &fsec);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL);
 
 	/* Use the specified time zone, if any. */
 	if (tm.tm_zone)
@@ -3705,6 +3883,10 @@ to_timestamp(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 				 errmsg("timestamp out of range")));
 
+	/* Use the specified fractional precision, if any. */
+	if (fprec)
+		AdjustTimestampForTypmod(&result, fprec);
+
 	PG_RETURN_TIMESTAMP(result);
 }
 
@@ -3722,7 +3904,7 @@ to_date(PG_FUNCTION_ARGS)
 	struct pg_tm tm;
 	fsec_t		fsec;
 
-	do_to_timestamp(date_txt, fmt, &tm, &fsec);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL);
 
 	/* Prevent overflow in Julian-day routines */
 	if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3743,11 +3925,169 @@ to_date(PG_FUNCTION_ARGS)
 	PG_RETURN_DATEADT(result);
 }
 
+/*
+ * Convert the 'date_txt' input to a datetime type using argument 'fmt' as a format string.
+ * The actual data type (returned in 'typid', 'typmod') is determined by
+ * the presence of date/time/zone components in the format string.
+ *
+ * When timezone component is present, the corresponding offset is set to '*tz'.
+ */
+Datum
+parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
+			   int32 *typmod, int *tz)
+{
+	struct pg_tm tm;
+	fsec_t		fsec;
+	int			fprec = 0;
+	int			flags;
+
+	do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags);
+
+	*typmod = fprec ? fprec : -1;	/* fractional part precision */
+
+	if (flags & DCH_DATED)
+	{
+		if (flags & DCH_TIMED)
+		{
+			if (flags & DCH_ZONED)
+			{
+				TimestampTz result;
+
+				if (tm.tm_zone)
+				{
+					int			dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
+
+					if (dterr)
+						DateTimeParseError(dterr, text_to_cstring(date_txt), "timestamptz");
+				}
+				else
+				{
+					if (*tz == PG_INT32_MIN)
+						ereport(ERROR,
+								(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								 errmsg("missing time zone in input string for type timestamptz")));
+				}
+
+				if (tm2timestamp(&tm, fsec, tz, &result) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("timestamptz out of range")));
+
+				AdjustTimestampForTypmod(&result, *typmod);
+
+				*typid = TIMESTAMPTZOID;
+				return TimestampTzGetDatum(result);
+			}
+			else
+			{
+				Timestamp	result;
+
+				if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("timestamp out of range")));
+
+				AdjustTimestampForTypmod(&result, *typmod);
+
+				*typid = TIMESTAMPOID;
+				return TimestampGetDatum(result);
+			}
+		}
+		else
+		{
+			if (flags & DCH_ZONED)
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+						 errmsg("datetime format is zoned but not timed")));
+			}
+			else
+			{
+				DateADT		result;
+
+				/* Prevent overflow in Julian-day routines */
+				if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("date out of range: \"%s\"",
+									text_to_cstring(date_txt))));
+
+				result = 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(result))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("date out of range: \"%s\"",
+									text_to_cstring(date_txt))));
+
+				*typid = DATEOID;
+				return DateADTGetDatum(result);
+			}
+		}
+	}
+	else if (flags & DCH_TIMED)
+	{
+		if (flags & DCH_ZONED)
+		{
+			TimeTzADT  *result = palloc(sizeof(TimeTzADT));
+
+			if (tm.tm_zone)
+			{
+				int			dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
+
+				if (dterr)
+					DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz");
+			}
+			else
+			{
+				if (*tz == PG_INT32_MIN)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							 errmsg("missing time zone in input string for type timetz")));
+			}
+
+			if (tm2timetz(&tm, fsec, *tz, result) != 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("timetz out of range")));
+
+			AdjustTimeForTypmod(&result->time, *typmod);
+
+			*typid = TIMETZOID;
+			return TimeTzADTPGetDatum(result);
+		}
+		else
+		{
+			TimeADT		result;
+
+			if (tm2time(&tm, fsec, &result) != 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("time out of range")));
+
+			AdjustTimeForTypmod(&result, *typmod);
+
+			*typid = TIMEOID;
+			return TimeADTGetDatum(result);
+		}
+	}
+	else
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+				 errmsg("datetime format is not dated and not timed")));
+	}
+
+	return (Datum) 0;
+}
+
 /*
  * do_to_timestamp: shared code for to_timestamp and to_date
  *
- * Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm
- * and fractional seconds.
+ * Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm,
+ * fractional seconds, and fractional precision.
  *
  * We parse 'fmt' into a list of FormatNodes, which is then passed to
  * DCH_from_char to populate a TmFromChar with the parsed contents of
@@ -3755,10 +4095,15 @@ to_date(PG_FUNCTION_ARGS)
  *
  * The TmFromChar is then analysed and converted into the final results in
  * struct 'tm' and 'fsec'.
+ *
+ * Bit mask of date/time/zone components found in 'fmt' is returned in 'flags'.
+ *
+ * 'strict' enables error reporting on unmatched trailing characters in input
+ * or format string patterns.
  */
 static void
-do_to_timestamp(text *date_txt, text *fmt,
-				struct pg_tm *tm, fsec_t *fsec)
+do_to_timestamp(text *date_txt, text *fmt, bool strict,
+				struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags)
 {
 	FormatNode *format;
 	TmFromChar	tmfc;
@@ -3811,9 +4156,13 @@ do_to_timestamp(text *date_txt, text *fmt,
 		/* dump_index(DCH_keywords, DCH_index); */
 #endif
 
-		DCH_from_char(format, date_str, &tmfc);
+		DCH_from_char(format, date_str, &tmfc, strict);
 
 		pfree(fmt_str);
+
+		if (flags)
+			*flags = DCH_datetime_type(format);
+
 		if (!incache)
 			pfree(format);
 	}
@@ -3995,6 +4344,8 @@ do_to_timestamp(text *date_txt, text *fmt,
 		*fsec += tmfc.ms * 1000;
 	if (tmfc.us)
 		*fsec += tmfc.us;
+	if (fprec)
+		*fprec = tmfc.ff;		/* fractional precision, if specified */
 
 	/* Range-check date fields according to bit mask computed above */
 	if (fmask != 0)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 5861ffbbc97..2931bd58c0d 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -70,7 +70,6 @@ typedef struct
 
 static TimeOffset time2t(const int hour, const int min, const int sec, const fsec_t fsec);
 static Timestamp dt2local(Timestamp dt, int timezone);
-static void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
 static void AdjustIntervalForTypmod(Interval *interval, int32 typmod);
 static TimestampTz timestamp2timestamptz(Timestamp timestamp);
 static Timestamp timestamptz2timestamp(TimestampTz timestamp);
@@ -333,7 +332,7 @@ timestamp_scale(PG_FUNCTION_ARGS)
  * AdjustTimestampForTypmod --- round off a timestamp to suit given typmod
  * Works for either timestamp or timestamptz.
  */
-static void
+void
 AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
 {
 	static const int64 TimestampScales[MAX_TIMESTAMP_PRECISION + 1] = {
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index bec129aff1c..bd15bfa5bb0 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -76,5 +76,8 @@ extern TimeTzADT *GetSQLCurrentTime(int32 typmod);
 extern TimeADT GetSQLLocalTime(int32 typmod);
 extern int	time2tm(TimeADT time, struct pg_tm *tm, fsec_t *fsec);
 extern int	timetz2tm(TimeTzADT *time, struct pg_tm *tm, fsec_t *fsec, int *tzp);
+extern int	tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
+extern int	tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
+extern void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
 
 #endif							/* DATE_H */
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index b8a199cdded..0cafdd26538 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -336,4 +336,6 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
 												   int n);
 extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
 
+extern void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
+
 #endif							/* DATETIME_H */
diff --git a/src/include/utils/formatting.h b/src/include/utils/formatting.h
index 5b275dc9850..a390302e89c 100644
--- a/src/include/utils/formatting.h
+++ b/src/include/utils/formatting.h
@@ -28,4 +28,7 @@ extern char *asc_tolower(const char *buff, size_t nbytes);
 extern char *asc_toupper(const char *buff, size_t nbytes);
 extern char *asc_initcap(const char *buff, size_t nbytes);
 
+extern Datum parse_datetime(text *date_txt, text *fmt, bool strict,
+							Oid *typid, int32 *typmod, int *tz);
+
 #endif
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index b2b45773339..74ecb7c10e6 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -2786,6 +2786,85 @@ SELECT to_timestamp('2011-12-18 11:38 20',     'YYYY-MM-DD HH12:MI TZM');
  Sun Dec 18 03:18:00 2011 PST
 (1 row)
 
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |         to_timestamp         
+---+------------------------------
+ 1 | Fri Nov 02 12:34:56 2018 PDT
+ 2 | Fri Nov 02 12:34:56 2018 PDT
+ 3 | Fri Nov 02 12:34:56 2018 PDT
+ 4 | Fri Nov 02 12:34:56 2018 PDT
+ 5 | Fri Nov 02 12:34:56 2018 PDT
+ 6 | Fri Nov 02 12:34:56 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |          to_timestamp          
+---+--------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.1 2018 PDT
+ 3 | Fri Nov 02 12:34:56.1 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |          to_timestamp           
+---+---------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.12 2018 PDT
+ 4 | Fri Nov 02 12:34:56.12 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |           to_timestamp           
+---+----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.123 2018 PDT
+ 5 | Fri Nov 02 12:34:56.123 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |           to_timestamp            
+---+-----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1234 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |            to_timestamp            
+---+------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12345 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12345 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |            to_timestamp             
+---+-------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12346 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123456 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ERROR:  date/time field value out of range: "2018-11-02 12:34:56.123456789"
 --
 -- Check handling of multiple spaces in format and/or input
 --
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index 715680e3302..f772b07d5a4 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -1584,6 +1584,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
             | 2001 1 1 1 1 1 1
 (65 rows)
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamp),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+ to_char_12 |                              to_char                               
+------------+--------------------------------------------------------------------
+            | 0 00 000 0000 00000 000000  0 00 000 0000 00000 000000  000 000000
+            | 7 78 780 7800 78000 780000  7 78 780 7800 78000 780000  780 780000
+            | 7 78 789 7890 78901 789010  7 78 789 7890 78901 789010  789 789010
+            | 7 78 789 7890 78901 789012  7 78 789 7890 78901 789012  789 789012
+(4 rows)
+
 -- timestamp numeric fields constructor
 SELECT make_timestamp(2014,12,28,6,30,45.887);
         make_timestamp        
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 5551fa6610e..2d6a71ca64b 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -1704,6 +1704,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
             | 2001 1 1 1 1 1 1
 (66 rows)
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamptz),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+ to_char_12 |                              to_char                               
+------------+--------------------------------------------------------------------
+            | 0 00 000 0000 00000 000000  0 00 000 0000 00000 000000  000 000000
+            | 7 78 780 7800 78000 780000  7 78 780 7800 78000 780000  780 780000
+            | 7 78 789 7890 78901 789010  7 78 789 7890 78901 789010  789 789010
+            | 7 78 789 7890 78901 789012  7 78 789 7890 78901 789012  789 789012
+(4 rows)
+
 -- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
 SET timezone = '00:00';
 SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index e356dd563ee..3c8580397ac 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -402,6 +402,15 @@ SELECT to_timestamp('2011-12-18 11:38 +05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
 SELECT to_timestamp('2011-12-18 11:38 -05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
 SELECT to_timestamp('2011-12-18 11:38 20',     'YYYY-MM-DD HH12:MI TZM');
 
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+
 --
 -- Check handling of multiple spaces in format and/or input
 --
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index 031b22bc3c1..329987f7eaa 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -224,5 +224,13 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
 SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
    FROM TIMESTAMP_TBL;
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamp),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+
 -- timestamp numeric fields constructor
 SELECT make_timestamp(2014,12,28,6,30,45.887);
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index 28c76d6b72c..f5fee639a01 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -248,6 +248,14 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
 SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
    FROM TIMESTAMPTZ_TBL;
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamptz),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+
 -- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
 SET timezone = '00:00';
 SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
0003-error-suppression-for-datetime-6.patchapplication/octet-stream; name=0003-error-suppression-for-datetime-6.patchDownload
commit 2866c9d84696eec367022fb3099a658502a6e30a
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Sun Jul 21 01:14:48 2019 +0300

    Error suppression support for upcoming jsonpath .datetime() method
    
    Add support of error suppression in some date and time manipulation functions
    as it's required for jsonpath .datetime() method support.  This commit doesn't
    use PG_TRY()/PG_CATCH() in order to implement that.  Instead, it provides
    internal versions of date and time functions used, which support error
    suppression.
    
    Discussion: https://postgr.es/m/CAPpHfdsZgYEra_PeCLGNoXOWYx6iU-S3wF8aX0ObQUcZU%2B4XTw%40mail.gmail.com
    Author: Alexander Korotkov, Nikita Glukhov
    Reviewed-by: Anastasia Lubennikova

diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 9e291b5c7bc..fa50d79c05d 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -550,13 +550,15 @@ date_mii(PG_FUNCTION_ARGS)
 	PG_RETURN_DATEADT(result);
 }
 
+
 /*
- * Internal routines for promoting date to timestamp and timestamp with
- * time zone
+ * Promote date to timestamp.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero is returned.
  */
-
-static Timestamp
-date2timestamp(DateADT dateVal)
+Timestamp
+date2timestamp_opt_error(DateADT dateVal, bool *have_error)
 {
 	Timestamp	result;
 
@@ -572,9 +574,19 @@ date2timestamp(DateADT dateVal)
 		 * boundary need be checked for overflow.
 		 */
 		if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("date out of range for timestamp")));
+		{
+			if (have_error)
+			{
+				*have_error = true;
+				return (Timestamp) 0;
+			}
+			else
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("date out of range for timestamp")));
+			}
+		}
 
 		/* date is days since 2000, timestamp is microseconds since same... */
 		result = dateVal * USECS_PER_DAY;
@@ -583,8 +595,23 @@ date2timestamp(DateADT dateVal)
 	return result;
 }
 
+/*
+ * Single-argument version of date2timestamp_opt_error().
+ */
 static TimestampTz
-date2timestamptz(DateADT dateVal)
+date2timestamp(DateADT dateVal)
+{
+	return date2timestamp_opt_error(dateVal, NULL);
+}
+
+/*
+ * Promote date to timestamp with time zone.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero is returned.
+ */
+TimestampTz
+date2timestamptz_opt_error(DateADT dateVal, bool *have_error)
 {
 	TimestampTz result;
 	struct pg_tm tt,
@@ -603,9 +630,19 @@ date2timestamptz(DateADT dateVal)
 		 * boundary need be checked for overflow.
 		 */
 		if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("date out of range for timestamp")));
+		{
+			if (have_error)
+			{
+				*have_error = true;
+				return (TimestampTz) 0;
+			}
+			else
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("date out of range for timestamp")));
+			}
+		}
 
 		j2date(dateVal + POSTGRES_EPOCH_JDATE,
 			   &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday));
@@ -621,14 +658,33 @@ date2timestamptz(DateADT dateVal)
 		 * of time zone, check for allowed timestamp range after adding tz.
 		 */
 		if (!IS_VALID_TIMESTAMP(result))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("date out of range for timestamp")));
+		{
+			if (have_error)
+			{
+				*have_error = true;
+				return (TimestampTz) 0;
+			}
+			else
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("date out of range for timestamp")));
+			}
+		}
 	}
 
 	return result;
 }
 
+/*
+ * Single-argument version of date2timestamptz_opt_error().
+ */
+static TimestampTz
+date2timestamptz(DateADT dateVal)
+{
+	return date2timestamptz_opt_error(dateVal, NULL);
+}
+
 /*
  * date2timestamp_no_overflow
  *
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index d06c778d2d2..d964a1d274d 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -98,6 +98,43 @@
 #include "utils/numeric.h"
 #include "utils/pg_locale.h"
 
+/* ----------
+ * Convenience macros for error handling
+ * ----------
+ *
+ * Two macros below help to handle errors in functions that take
+ * 'bool *have_error' argument.  When this argument is not NULL, it's expected
+ * that function will suppress ereports when possible.  Instead it should
+ * return some default value and set *have_error flag.
+ *
+ * RETURN_ERROR() macro intended to wrap ereport() calls.  When have_error
+ * function argument is not NULL, then instead of ereport'ing we set
+ * *have_error flag and go to on_error label.  It's supposed that jump
+ * resources will be freed and some 'default' value returned.
+ *
+ * CHECK_ERROR() jumps on_error label when *have_error flag is defined and set.
+ * It's supposed to be used for immediate exit from the function on error
+ * after call of another function with 'bool *have_error' argument.
+ */
+#define RETURN_ERROR(throw_error) \
+do { \
+	if (have_error) \
+	{ \
+		*have_error = true; \
+		goto on_error; \
+	} \
+	else \
+	{ \
+		throw_error; \
+	} \
+} while (0)
+
+#define CHECK_ERROR \
+do { \
+	if (have_error && *have_error) \
+		goto on_error; \
+} while (0)
+
 /* ----------
  * Routines type
  * ----------
@@ -1006,7 +1043,7 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 static void DCH_to_char(FormatNode *node, bool is_interval,
 						TmToChar *in, char *out, Oid collid);
 static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
-						  bool strict);
+						  bool strict, bool *have_error);
 
 #ifdef DEBUG_TO_FROM_CHAR
 static void dump_index(const KeyWord *k, const int *index);
@@ -1017,14 +1054,21 @@ static const char *get_th(char *num, int type);
 static char *str_numth(char *dest, char *num, int type);
 static int	adjust_partial_year_to_2020(int year);
 static int	strspace_len(char *str);
-static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode);
-static void from_char_set_int(int *dest, const int value, const FormatNode *node);
-static int	from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node);
-static int	from_char_parse_int(int *dest, char **src, FormatNode *node);
+static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode,
+							   bool *have_error);
+static void from_char_set_int(int *dest, const int value, const FormatNode *node,
+							  bool *have_error);
+static int	from_char_parse_int_len(int *dest, char **src, const int len,
+									FormatNode *node, bool *have_error);
+static int	from_char_parse_int(int *dest, char **src, FormatNode *node,
+								bool *have_error);
 static int	seq_search(char *name, const char *const *array, int type, int max, int *len);
-static int	from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
+static int	from_char_seq_search(int *dest, char **src,
+								 const char *const *array, int type, int max,
+								 FormatNode *node, bool *have_error);
 static void do_to_timestamp(text *date_txt, text *fmt, bool strict,
-							struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags);
+							struct pg_tm *tm, fsec_t *fsec, int *fprec,
+							int *flags, bool *have_error);
 static char *fill_str(char *str, int c, int max);
 static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
 static char *int_to_roman(int number);
@@ -2200,21 +2244,26 @@ strspace_len(char *str)
  *
  * Puke if the date mode has already been set, and the caller attempts to set
  * it to a conflicting mode.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
-from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode)
+from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode, bool *have_error)
 {
 	if (mode != FROM_CHAR_DATE_NONE)
 	{
 		if (tmfc->mode == FROM_CHAR_DATE_NONE)
 			tmfc->mode = mode;
 		else if (tmfc->mode != mode)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("invalid combination of date conventions"),
-					 errhint("Do not mix Gregorian and ISO week date "
-							 "conventions in a formatting template.")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("invalid combination of date conventions"),
+								  errhint("Do not mix Gregorian and ISO week date "
+										  "conventions in a formatting template."))));
 	}
+
+on_error:
+	return;
 }
 
 /*
@@ -2222,18 +2271,25 @@ from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode)
  *
  * Puke if the destination integer has previously been set to some other
  * non-zero value.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
-from_char_set_int(int *dest, const int value, const FormatNode *node)
+from_char_set_int(int *dest, const int value, const FormatNode *node,
+				  bool *have_error)
 {
 	if (*dest != 0 && *dest != value)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("conflicting values for \"%s\" field in formatting string",
-						node->key->name),
-				 errdetail("This value contradicts a previous setting for "
-						   "the same field type.")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("conflicting values for \"%s\" field in "
+									 "formatting string",
+									 node->key->name),
+							  errdetail("This value contradicts a previous setting "
+										"for the same field type."))));
 	*dest = value;
+
+on_error:
+	return;
 }
 
 /*
@@ -2255,9 +2311,13 @@ from_char_set_int(int *dest, const int value, const FormatNode *node)
  * Note that from_char_parse_int() provides a more convenient wrapper where
  * the length of the field is the same as the length of the format keyword (as
  * with DD and MI).
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and -1 is returned.
  */
 static int
-from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
+from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node,
+						bool *have_error)
 {
 	long		result;
 	char		copy[DCH_MAX_ITEM_SIZ + 1];
@@ -2290,51 +2350,60 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
 		char	   *last;
 
 		if (used < len)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("source string too short for \"%s\" formatting field",
-							node->key->name),
-					 errdetail("Field requires %d characters, but only %d "
-							   "remain.",
-							   len, used),
-					 errhint("If your source string is not fixed-width, try "
-							 "using the \"FM\" modifier.")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("source string too short for \"%s\" "
+										 "formatting field",
+										 node->key->name),
+								  errdetail("Field requires %d characters, "
+											"but only %d remain.",
+											len, used),
+								  errhint("If your source string is not fixed-width, "
+										  "try using the \"FM\" modifier."))));
 
 		errno = 0;
 		result = strtol(copy, &last, 10);
 		used = last - copy;
 
 		if (used > 0 && used < len)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("invalid value \"%s\" for \"%s\"",
-							copy, node->key->name),
-					 errdetail("Field requires %d characters, but only %d "
-							   "could be parsed.", len, used),
-					 errhint("If your source string is not fixed-width, try "
-							 "using the \"FM\" modifier.")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("invalid value \"%s\" for \"%s\"",
+										 copy, node->key->name),
+								  errdetail("Field requires %d characters, "
+											"but only %d could be parsed.",
+											len, used),
+								  errhint("If your source string is not fixed-width, "
+										  "try using the \"FM\" modifier."))));
 
 		*src += used;
 	}
 
 	if (*src == init)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("invalid value \"%s\" for \"%s\"",
-						copy, node->key->name),
-				 errdetail("Value must be an integer.")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("invalid value \"%s\" for \"%s\"",
+									 copy, node->key->name),
+							  errdetail("Value must be an integer."))));
 
 	if (errno == ERANGE || result < INT_MIN || result > INT_MAX)
-		ereport(ERROR,
-				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-				 errmsg("value for \"%s\" in source string is out of range",
-						node->key->name),
-				 errdetail("Value must be in the range %d to %d.",
-						   INT_MIN, INT_MAX)));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							  errmsg("value for \"%s\" in source string is out of range",
+									 node->key->name),
+							  errdetail("Value must be in the range %d to %d.",
+										INT_MIN, INT_MAX))));
 
 	if (dest != NULL)
-		from_char_set_int(dest, (int) result, node);
+	{
+		from_char_set_int(dest, (int) result, node, have_error);
+		CHECK_ERROR;
+	}
+
 	return *src - init;
+
+on_error:
+	return -1;
 }
 
 /*
@@ -2347,9 +2416,9 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
  * required length explicitly.
  */
 static int
-from_char_parse_int(int *dest, char **src, FormatNode *node)
+from_char_parse_int(int *dest, char **src, FormatNode *node, bool *have_error)
 {
-	return from_char_parse_int_len(dest, src, node->key->len, node);
+	return from_char_parse_int_len(dest, src, node->key->len, node, have_error);
 }
 
 /* ----------
@@ -2432,11 +2501,12 @@ seq_search(char *name, const char *const *array, int type, int max, int *len)
  * pointed to by 'dest', advance 'src' to the end of the part of the string
  * which matched, and return the number of characters consumed.
  *
- * If the string doesn't match, throw an error.
+ * If the string doesn't match, throw an error if 'have_error' is NULL,
+ * otherwise set '*have_error' and return -1.
  */
 static int
-from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max,
-					 FormatNode *node)
+from_char_seq_search(int *dest, char **src, const char *const *array, int type,
+					 int max, FormatNode *node, bool *have_error)
 {
 	int			len;
 
@@ -2448,15 +2518,18 @@ from_char_seq_search(int *dest, char **src, const char *const *array, int type,
 		Assert(max <= DCH_MAX_ITEM_SIZ);
 		strlcpy(copy, *src, max + 1);
 
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("invalid value \"%s\" for \"%s\"",
-						copy, node->key->name),
-				 errdetail("The given value did not match any of the allowed "
-						   "values for this field.")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("invalid value \"%s\" for \"%s\"",
+									 copy, node->key->name),
+							  errdetail("The given value did not match any of "
+										"the allowed values for this field."))));
 	}
 	*src += len;
 	return len;
+
+on_error:
+	return -1;
 }
 
 /* ----------
@@ -3058,10 +3131,13 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
  *
  * Note: we currently don't have any to_interval() function, so there
  * is no need here for INVALID_FOR_INTERVAL checks.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  * ----------
  */
 static void
-DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
+DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict,
+			  bool *have_error)
 {
 	FormatNode *n;
 	char	   *s;
@@ -3144,7 +3220,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 			continue;
 		}
 
-		from_char_set_mode(out, n->key->date_mode);
+		from_char_set_mode(out, n->key->date_mode, have_error);
+		CHECK_ERROR;
 
 		switch (n->key->id)
 		{
@@ -3156,8 +3233,10 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 			case DCH_a_m:
 			case DCH_p_m:
 				from_char_seq_search(&value, &s, ampm_strings_long,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->pm, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->pm, value % 2, n, have_error);
+				CHECK_ERROR;
 				out->clock = CLOCK_12_HOUR;
 				break;
 			case DCH_AM:
@@ -3165,30 +3244,37 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 			case DCH_am:
 			case DCH_pm:
 				from_char_seq_search(&value, &s, ampm_strings,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->pm, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->pm, value % 2, n, have_error);
+				CHECK_ERROR;
 				out->clock = CLOCK_12_HOUR;
 				break;
 			case DCH_HH:
 			case DCH_HH12:
-				from_char_parse_int_len(&out->hh, &s, 2, n);
+				from_char_parse_int_len(&out->hh, &s, 2, n, have_error);
+				CHECK_ERROR;
 				out->clock = CLOCK_12_HOUR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_HH24:
-				from_char_parse_int_len(&out->hh, &s, 2, n);
+				from_char_parse_int_len(&out->hh, &s, 2, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_MI:
-				from_char_parse_int(&out->mi, &s, n);
+				from_char_parse_int(&out->mi, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_SS:
-				from_char_parse_int(&out->ss, &s, n);
+				from_char_parse_int(&out->ss, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_MS:		/* millisecond */
-				len = from_char_parse_int_len(&out->ms, &s, 3, n);
+				len = from_char_parse_int_len(&out->ms, &s, 3, n, have_error);
+				CHECK_ERROR;
 
 				/*
 				 * 25 is 0.25 and 250 is 0.25 too; 025 is 0.025 and not 0.25
@@ -3209,7 +3295,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 			case DCH_US:		/* microsecond */
 				len = from_char_parse_int_len(&out->us, &s,
 											  n->key->id == DCH_US ? 6 :
-											  out->ff, n);
+											  out->ff, n, have_error);
+				CHECK_ERROR;
 
 				out->us *= len == 1 ? 100000 :
 					len == 2 ? 10000 :
@@ -3220,16 +3307,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_SSSS:
-				from_char_parse_int(&out->ssss, &s, n);
+				from_char_parse_int(&out->ssss, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_tz:
 			case DCH_TZ:
 			case DCH_OF:
-				ereport(ERROR,
-						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						 errmsg("formatting field \"%s\" is only supported in to_char",
-								n->key->name)));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									  errmsg("formatting field \"%s\" is only supported in to_char",
+											 n->key->name))));
+				CHECK_ERROR;
 				break;
 			case DCH_TZH:
 
@@ -3253,82 +3342,102 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 						out->tzsign = +1;
 				}
 
-				from_char_parse_int_len(&out->tzh, &s, 2, n);
+				from_char_parse_int_len(&out->tzh, &s, 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_TZM:
 				/* assign positive timezone sign if TZH was not seen before */
 				if (!out->tzsign)
 					out->tzsign = +1;
-				from_char_parse_int_len(&out->tzm, &s, 2, n);
+				from_char_parse_int_len(&out->tzm, &s, 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_A_D:
 			case DCH_B_C:
 			case DCH_a_d:
 			case DCH_b_c:
 				from_char_seq_search(&value, &s, adbc_strings_long,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->bc, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->bc, value % 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_AD:
 			case DCH_BC:
 			case DCH_ad:
 			case DCH_bc:
 				from_char_seq_search(&value, &s, adbc_strings,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->bc, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->bc, value % 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_MONTH:
 			case DCH_Month:
 			case DCH_month:
 				from_char_seq_search(&value, &s, months_full, ONE_UPPER,
-									 MAX_MONTH_LEN, n);
-				from_char_set_int(&out->mm, value + 1, n);
+									 MAX_MONTH_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, value + 1, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_MON:
 			case DCH_Mon:
 			case DCH_mon:
 				from_char_seq_search(&value, &s, months, ONE_UPPER,
-									 MAX_MON_LEN, n);
-				from_char_set_int(&out->mm, value + 1, n);
+									 MAX_MON_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, value + 1, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_MM:
-				from_char_parse_int(&out->mm, &s, n);
+				from_char_parse_int(&out->mm, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_DAY:
 			case DCH_Day:
 			case DCH_day:
 				from_char_seq_search(&value, &s, days, ONE_UPPER,
-									 MAX_DAY_LEN, n);
-				from_char_set_int(&out->d, value, n);
+									 MAX_DAY_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->d, value, n, have_error);
+				CHECK_ERROR;
 				out->d++;
 				break;
 			case DCH_DY:
 			case DCH_Dy:
 			case DCH_dy:
 				from_char_seq_search(&value, &s, days, ONE_UPPER,
-									 MAX_DY_LEN, n);
-				from_char_set_int(&out->d, value, n);
+									 MAX_DY_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->d, value, n, have_error);
+				CHECK_ERROR;
 				out->d++;
 				break;
 			case DCH_DDD:
-				from_char_parse_int(&out->ddd, &s, n);
+				from_char_parse_int(&out->ddd, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_IDDD:
-				from_char_parse_int_len(&out->ddd, &s, 3, n);
+				from_char_parse_int_len(&out->ddd, &s, 3, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_DD:
-				from_char_parse_int(&out->dd, &s, n);
+				from_char_parse_int(&out->dd, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_D:
-				from_char_parse_int(&out->d, &s, n);
+				from_char_parse_int(&out->d, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_ID:
-				from_char_parse_int_len(&out->d, &s, 1, n);
+				from_char_parse_int_len(&out->d, &s, 1, n, have_error);
+				CHECK_ERROR;
 				/* Shift numbering to match Gregorian where Sunday = 1 */
 				if (++out->d > 7)
 					out->d = 1;
@@ -3336,7 +3445,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 				break;
 			case DCH_WW:
 			case DCH_IW:
-				from_char_parse_int(&out->ww, &s, n);
+				from_char_parse_int(&out->ww, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Q:
@@ -3351,11 +3461,13 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 				 * We still parse the source string for an integer, but it
 				 * isn't stored anywhere in 'out'.
 				 */
-				from_char_parse_int((int *) NULL, &s, n);
+				from_char_parse_int((int *) NULL, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_CC:
-				from_char_parse_int(&out->cc, &s, n);
+				from_char_parse_int(&out->cc, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Y_YYY:
@@ -3367,11 +3479,12 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 
 					matched = sscanf(s, "%d,%03d%n", &millennia, &years, &nch);
 					if (matched < 2)
-						ereport(ERROR,
-								(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-								 errmsg("invalid input string for \"Y,YYY\"")));
+						RETURN_ERROR(ereport(ERROR,
+											 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+											  errmsg("invalid input string for \"Y,YYY\""))));
 					years += (millennia * 1000);
-					from_char_set_int(&out->year, years, n);
+					from_char_set_int(&out->year, years, n, have_error);
+					CHECK_ERROR;
 					out->yysz = 4;
 					s += nch;
 					SKIP_THth(s, n->suffix);
@@ -3379,47 +3492,62 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 				break;
 			case DCH_YYYY:
 			case DCH_IYYY:
-				from_char_parse_int(&out->year, &s, n);
+				from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
 				out->yysz = 4;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_YYY:
 			case DCH_IYY:
-				if (from_char_parse_int(&out->year, &s, n) < 4)
+				len = from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
+				if (len < 4)
 					out->year = adjust_partial_year_to_2020(out->year);
 				out->yysz = 3;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_YY:
 			case DCH_IY:
-				if (from_char_parse_int(&out->year, &s, n) < 4)
+				len = from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
+				if (len < 4)
 					out->year = adjust_partial_year_to_2020(out->year);
 				out->yysz = 2;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Y:
 			case DCH_I:
-				if (from_char_parse_int(&out->year, &s, n) < 4)
+				len = from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
+				if (len < 4)
 					out->year = adjust_partial_year_to_2020(out->year);
 				out->yysz = 1;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_RM:
 				from_char_seq_search(&value, &s, rm_months_upper,
-									 ALL_UPPER, MAX_RM_LEN, n);
-				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n);
+									 ALL_UPPER, MAX_RM_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value,
+								  n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_rm:
 				from_char_seq_search(&value, &s, rm_months_lower,
-									 ALL_LOWER, MAX_RM_LEN, n);
-				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n);
+									 ALL_LOWER, MAX_RM_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value,
+								  n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_W:
-				from_char_parse_int(&out->w, &s, n);
+				from_char_parse_int(&out->w, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_J:
-				from_char_parse_int(&out->j, &s, n);
+				from_char_parse_int(&out->j, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 		}
@@ -3439,19 +3567,22 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
 	if (strict)
 	{
 		if (n->type != NODE_TYPE_END)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("input string is too short for datetime format")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("input string is too short for datetime format"))));
 
 		while (*s != '\0' && isspace((unsigned char) *s))
 			s++;
 
 		if (*s != '\0')
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("trailing characters remain in input string after "
-							"datetime format")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("trailing characters remain in input string "
+										 "after datetime format"))));
 	}
+
+on_error:
+	return;
 }
 
 /*
@@ -3472,9 +3603,13 @@ DCH_prevent_counter_overflow(void)
 	}
 }
 
-/* Get mask of date/time/zone components present in format nodes. */
+/*
+ * Get mask of date/time/zone components present in format nodes.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
+ */
 static int
-DCH_datetime_type(FormatNode *node)
+DCH_datetime_type(FormatNode *node, bool *have_error)
 {
 	FormatNode *n;
 	int			flags = 0;
@@ -3515,10 +3650,10 @@ DCH_datetime_type(FormatNode *node)
 			case DCH_tz:
 			case DCH_TZ:
 			case DCH_OF:
-				ereport(ERROR,
-						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						 errmsg("formatting field \"%s\" is only supported in to_char",
-								n->key->name)));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									  errmsg("formatting field \"%s\" is only supported in to_char",
+											 n->key->name))));
 				flags |= DCH_ZONED;
 				break;
 			case DCH_TZH:
@@ -3572,6 +3707,7 @@ DCH_datetime_type(FormatNode *node)
 		}
 	}
 
+on_error:
 	return flags;
 }
 
@@ -3865,7 +4001,7 @@ to_timestamp(PG_FUNCTION_ARGS)
 	fsec_t		fsec;
 	int			fprec;
 
-	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL, NULL);
 
 	/* Use the specified time zone, if any. */
 	if (tm.tm_zone)
@@ -3904,7 +4040,7 @@ to_date(PG_FUNCTION_ARGS)
 	struct pg_tm tm;
 	fsec_t		fsec;
 
-	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL, NULL);
 
 	/* Prevent overflow in Julian-day routines */
 	if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3931,17 +4067,21 @@ to_date(PG_FUNCTION_ARGS)
  * the presence of date/time/zone components in the format string.
  *
  * When timezone component is present, the corresponding offset is set to '*tz'.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero value is returned.
  */
 Datum
 parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
-			   int32 *typmod, int *tz)
+			   int32 *typmod, int *tz, bool *have_error)
 {
 	struct pg_tm tm;
 	fsec_t		fsec;
 	int			fprec = 0;
 	int			flags;
 
-	do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags);
+	do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags, have_error);
+	CHECK_ERROR;
 
 	*typmod = fprec ? fprec : -1;	/* fractional part precision */
 
@@ -3962,16 +4102,22 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 				}
 				else
 				{
-					if (*tz == PG_INT32_MIN)
-						ereport(ERROR,
-								(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-								 errmsg("missing time zone in input string for type timestamptz")));
+					/*
+					 * Time zone is present in format string, but not in input
+					 * string.  Assuming do_to_timestamp() triggers no error
+					 * this should be possible only in non-strict case.
+					 */
+					Assert(!strict);
+
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+										  errmsg("missing time zone in input string for type timestamptz"))));
 				}
 
 				if (tm2timestamp(&tm, fsec, tz, &result) != 0)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("timestamptz out of range")));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("timestamptz out of range"))));
 
 				AdjustTimestampForTypmod(&result, *typmod);
 
@@ -3983,9 +4129,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 				Timestamp	result;
 
 				if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("timestamp out of range")));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("timestamp out of range"))));
 
 				AdjustTimestampForTypmod(&result, *typmod);
 
@@ -3997,9 +4143,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 		{
 			if (flags & DCH_ZONED)
 			{
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-						 errmsg("datetime format is zoned but not timed")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+									  errmsg("datetime format is zoned but not timed"))));
 			}
 			else
 			{
@@ -4007,20 +4153,20 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 
 				/* Prevent overflow in Julian-day routines */
 				if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("date out of range: \"%s\"",
-									text_to_cstring(date_txt))));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("date out of range: \"%s\"",
+												 text_to_cstring(date_txt)))));
 
 				result = 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(result))
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("date out of range: \"%s\"",
-									text_to_cstring(date_txt))));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("date out of range: \"%s\"",
+												 text_to_cstring(date_txt)))));
 
 				*typid = DATEOID;
 				return DateADTGetDatum(result);
@@ -4038,20 +4184,26 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 				int			dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
 
 				if (dterr)
-					DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz");
+					RETURN_ERROR(DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz"));
 			}
 			else
 			{
-				if (*tz == PG_INT32_MIN)
-					ereport(ERROR,
-							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-							 errmsg("missing time zone in input string for type timetz")));
+				/*
+				 * Time zone is present in format string, but not in input
+				 * string.  Assuming do_to_timestamp() triggers no error this
+				 * should be possible only in non-strict case.
+				 */
+				Assert(!strict);
+
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+									  errmsg("missing time zone in input string for type timetz"))));
 			}
 
 			if (tm2timetz(&tm, fsec, *tz, result) != 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-						 errmsg("timetz out of range")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+									  errmsg("timetz out of range"))));
 
 			AdjustTimeForTypmod(&result->time, *typmod);
 
@@ -4063,9 +4215,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 			TimeADT		result;
 
 			if (tm2time(&tm, fsec, &result) != 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-						 errmsg("time out of range")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+									  errmsg("time out of range"))));
 
 			AdjustTimeForTypmod(&result, *typmod);
 
@@ -4075,11 +4227,12 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 	}
 	else
 	{
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("datetime format is not dated and not timed")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("datetime format is not dated and not timed"))));
 	}
 
+on_error:
 	return (Datum) 0;
 }
 
@@ -4100,16 +4253,19 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
  *
  * 'strict' enables error reporting on unmatched trailing characters in input
  * or format string patterns.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
-do_to_timestamp(text *date_txt, text *fmt, bool strict,
-				struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags)
+do_to_timestamp(text *date_txt, text *fmt, bool strict, struct pg_tm *tm,
+				fsec_t *fsec, int *fprec, int *flags, bool *have_error)
 {
-	FormatNode *format;
+	FormatNode *format = NULL;
 	TmFromChar	tmfc;
 	int			fmt_len;
 	char	   *date_str;
 	int			fmask;
+	bool		incache = false;
 
 	date_str = text_to_cstring(date_txt);
 
@@ -4123,7 +4279,6 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 	if (fmt_len)
 	{
 		char	   *fmt_str;
-		bool		incache;
 
 		fmt_str = text_to_cstring(fmt);
 
@@ -4133,8 +4288,6 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 			 * Allocate new memory if format picture is bigger than static
 			 * cache and do not use cache (call parser always)
 			 */
-			incache = false;
-
 			format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode));
 
 			parse_format(format, fmt_str, DCH_keywords,
@@ -4156,15 +4309,21 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 		/* dump_index(DCH_keywords, DCH_index); */
 #endif
 
-		DCH_from_char(format, date_str, &tmfc, strict);
+		DCH_from_char(format, date_str, &tmfc, strict, have_error);
+		CHECK_ERROR;
 
 		pfree(fmt_str);
 
 		if (flags)
-			*flags = DCH_datetime_type(format);
+			*flags = DCH_datetime_type(format, have_error);
 
 		if (!incache)
+		{
 			pfree(format);
+			format = NULL;
+		}
+
+		CHECK_ERROR;
 	}
 
 	DEBUG_TMFC(&tmfc);
@@ -4193,11 +4352,13 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 	if (tmfc.clock == CLOCK_12_HOUR)
 	{
 		if (tm->tm_hour < 1 || tm->tm_hour > HOURS_PER_DAY / 2)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("hour \"%d\" is invalid for the 12-hour clock",
-							tm->tm_hour),
-					 errhint("Use the 24-hour clock, or give an hour between 1 and 12.")));
+		{
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("hour \"%d\" is invalid for the 12-hour clock",
+										 tm->tm_hour),
+								  errhint("Use the 24-hour clock, or give an hour between 1 and 12."))));
+		}
 
 		if (tmfc.pm && tm->tm_hour < HOURS_PER_DAY / 2)
 			tm->tm_hour += HOURS_PER_DAY / 2;
@@ -4301,9 +4462,11 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 		 */
 
 		if (!tm->tm_year && !tmfc.bc)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("cannot calculate day of year without year information")));
+		{
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("cannot calculate day of year without year information"))));
+		}
 
 		if (tmfc.mode == FROM_CHAR_DATE_ISOWEEK)
 		{
@@ -4360,7 +4523,7 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 			 * said DTERR_MD_FIELD_OVERFLOW, because we don't want to print an
 			 * irrelevant hint about datestyle.
 			 */
-			DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp");
+			RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp"));
 		}
 	}
 
@@ -4369,7 +4532,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 		tm->tm_min < 0 || tm->tm_min >= MINS_PER_HOUR ||
 		tm->tm_sec < 0 || tm->tm_sec >= SECS_PER_MINUTE ||
 		*fsec < INT64CONST(0) || *fsec >= USECS_PER_SEC)
-		DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp");
+	{
+		RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp"));
+	}
 
 	/* Save parsed time-zone into tm->tm_zone if it was specified */
 	if (tmfc.tzsign)
@@ -4378,7 +4543,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 
 		if (tmfc.tzh < 0 || tmfc.tzh > MAX_TZDISP_HOUR ||
 			tmfc.tzm < 0 || tmfc.tzm >= MINS_PER_HOUR)
-			DateTimeParseError(DTERR_TZDISP_OVERFLOW, date_str, "timestamp");
+		{
+			RETURN_ERROR(DateTimeParseError(DTERR_TZDISP_OVERFLOW, date_str, "timestamp"));
+		}
 
 		tz = psprintf("%c%02d:%02d",
 					  tmfc.tzsign > 0 ? '+' : '-', tmfc.tzh, tmfc.tzm);
@@ -4388,6 +4555,11 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
 
 	DEBUG_TM(tm);
 
+on_error:
+
+	if (format && !incache)
+		pfree(format);
+
 	pfree(date_str);
 }
 
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 2931bd58c0d..84bc97d40c3 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -329,11 +329,11 @@ timestamp_scale(PG_FUNCTION_ARGS)
 }
 
 /*
- * AdjustTimestampForTypmod --- round off a timestamp to suit given typmod
+ * AdjustTimestampForTypmodError --- round off a timestamp to suit given typmod
  * Works for either timestamp or timestamptz.
  */
-void
-AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
+bool
+AdjustTimestampForTypmodError(Timestamp *time, int32 typmod, bool *error)
 {
 	static const int64 TimestampScales[MAX_TIMESTAMP_PRECISION + 1] = {
 		INT64CONST(1000000),
@@ -359,10 +359,18 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
 		&& (typmod != -1) && (typmod != MAX_TIMESTAMP_PRECISION))
 	{
 		if (typmod < 0 || typmod > MAX_TIMESTAMP_PRECISION)
+		{
+			if (error)
+			{
+				*error = true;
+				return false;
+			}
+
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("timestamp(%d) precision must be between %d and %d",
 							typmod, 0, MAX_TIMESTAMP_PRECISION)));
+		}
 
 		if (*time >= INT64CONST(0))
 		{
@@ -375,8 +383,15 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
 					  * TimestampScales[typmod]);
 		}
 	}
+
+	return true;
 }
 
+void
+AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
+{
+	(void) AdjustTimestampForTypmodError(time, typmod, NULL);
+}
 
 /* timestamptz_in()
  * Convert a string to internal form.
@@ -5172,8 +5187,15 @@ timestamp_timestamptz(PG_FUNCTION_ARGS)
 	PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
 }
 
-static TimestampTz
-timestamp2timestamptz(Timestamp timestamp)
+/*
+ * Convert timestamp to timestamp with time zone.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero is returned.
+ */
+
+TimestampTz
+timestamp2timestamptz_opt_error(Timestamp timestamp, bool *have_error)
 {
 	TimestampTz result;
 	struct pg_tm tt,
@@ -5182,23 +5204,33 @@ timestamp2timestamptz(Timestamp timestamp)
 	int			tz;
 
 	if (TIMESTAMP_NOT_FINITE(timestamp))
-		result = timestamp;
-	else
-	{
-		if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("timestamp out of range")));
+		return timestamp;
 
+	if (!timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL))
+	{
 		tz = DetermineTimeZoneOffset(tm, session_timezone);
 
-		if (tm2timestamp(tm, fsec, &tz, &result) != 0)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("timestamp out of range")));
+		if (!tm2timestamp(tm, fsec, &tz, &result))
+			return result;
 	}
 
-	return result;
+	if (have_error)
+		*have_error = true;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+				 errmsg("timestamp out of range")));
+
+	return 0;
+}
+
+/*
+ * Single-argument version of timestamp2timestamptz_opt_error().
+ */
+static TimestampTz
+timestamp2timestamptz(Timestamp timestamp)
+{
+	return timestamp2timestamptz_opt_error(timestamp, NULL);
 }
 
 /* timestamptz_timestamp()
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index bd15bfa5bb0..c29f13aaf04 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -70,6 +70,8 @@ typedef struct
 /* date.c */
 extern int32 anytime_typmod_check(bool istz, int32 typmod);
 extern double date2timestamp_no_overflow(DateADT dateVal);
+extern Timestamp date2timestamp_opt_error(DateADT dateVal, bool *have_error);
+extern TimestampTz date2timestamptz_opt_error(DateADT dateVal, bool *have_error);
 extern void EncodeSpecialDate(DateADT dt, char *str);
 extern DateADT GetSQLCurrentDate(void);
 extern TimeTzADT *GetSQLCurrentTime(int32 typmod);
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index 0cafdd26538..5ebf336071b 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -337,5 +337,7 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
 extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
 
 extern void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
+extern bool AdjustTimestampForTypmodError(Timestamp *time, int32 typmod,
+										  bool *error);
 
 #endif							/* DATETIME_H */
diff --git a/src/include/utils/formatting.h b/src/include/utils/formatting.h
index a390302e89c..1a2e2a9caa8 100644
--- a/src/include/utils/formatting.h
+++ b/src/include/utils/formatting.h
@@ -29,6 +29,7 @@ extern char *asc_toupper(const char *buff, size_t nbytes);
 extern char *asc_initcap(const char *buff, size_t nbytes);
 
 extern Datum parse_datetime(text *date_txt, text *fmt, bool strict,
-							Oid *typid, int32 *typmod, int *tz);
+							Oid *typid, int32 *typmod, int *tz,
+							bool *have_error);
 
 #endif
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index ea16190ec3d..e884d4405f5 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -97,6 +97,9 @@ extern int	timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
 /* timestamp comparison works for timestamptz also */
 #define timestamptz_cmp_internal(dt1,dt2)	timestamp_cmp_internal(dt1, dt2)
 
+extern TimestampTz timestamp2timestamptz_opt_error(Timestamp timestamp,
+												   bool *have_error);
+
 extern int	isoweek2j(int year, int week);
 extern void isoweek2date(int woy, int *year, int *mon, int *mday);
 extern void isoweekdate2date(int isoweek, int wday, int *year, int *mon, int *mday);
0004-implement-jsonpath-datetime-6.patchapplication/octet-stream; name=0004-implement-jsonpath-datetime-6.patchDownload
commit 7c49a6f70c2005edcaea896bcb93e31a4bc1612a
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Sun Jul 21 01:16:38 2019 +0300

    Implement jsonpath .datetime() method
    
    This commit implements jsonpath .datetime() method as it's specified in
    SQL/JSON standard.  There are no-argument and single-argument versions of
    this method.  No-argument version selects first of ISO datetime formats
    matching input string.  Single-argument version accepts template string as
    its argument.
    
    Additionally to .datetime() method itself this commit also implements
    comparison ability of resulting date and time values.  There is some difficulty
    because exising jsonb_path_*() functions are immutable, while comparison of
    timezoned and non-timezoned types involves current timezone.  At first, current
    timezone could be changes in session.  Moreover, timezones themselves are not
    immutable and could be updated.  This is why we let existing immutable functions
    throw errors on such non-immutable comparison.  In the same time this commit
    provides jsonb_path_*_tz() functions which are stable and support operations
    involving timezones.
    
    Support of .datetime() method was the only blocker prevents T832 from being
    marked as supported.  sql_features.txt is updated correspondingly.
    
    Extracted from original patch by Nikita Glukhov, Teodor Sigaev, Oleg Bartunov.
    Heavily revised by me.  Comments were adjusted by Liudmila Mantrova.
    
    Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
    Discussion: https://postgr.es/m/CAPpHfdsZgYEra_PeCLGNoXOWYx6iU-S3wF8aX0ObQUcZU%2B4XTw%40mail.gmail.com
    Author: Alexander Korotkov, Nikita Glukhov, Teodor Sigaev, Oleg Bartunov, Liudmila Mantrova
    Reviewed-by: Anastasia Lubennikova

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index afde81664bb..778a8867107 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -11744,16 +11744,6 @@ table2-mapping
   </para>
 
   <itemizedlist>
-   <listitem>
-    <para>
-     <literal>.datetime()</literal> item method is not implemented yet
-     mainly because immutable <type>jsonpath</type> functions and operators
-     cannot reference session timezone, which is used in some datetime
-     operations.  Datetime support will be added to <type>jsonpath</type>
-     in future versions of <productname>PostgreSQL</productname>.
-    </para>
-   </listitem>
-
    <listitem>
     <para>
      A path expression can be a Boolean predicate, although the SQL/JSON
@@ -11959,6 +11949,20 @@ table2-mapping
         <entry><literal>$.z.abs()</literal></entry>
         <entry><literal>0.3</literal></entry>
        </row>
+       <row>
+        <entry><literal>datetime()</literal></entry>
+        <entry>Date/time value converted from a string</entry>
+        <entry><literal>["2015-8-1", "2015-08-12"]</literal></entry>
+        <entry><literal>$[*] ? (@.datetime() &lt; "2015-08-2". datetime())</literal></entry>
+        <entry><literal>2015-8-1</literal></entry>
+       </row>
+       <row>
+        <entry><literal>datetime(<replaceable>template</replaceable>)</literal></entry>
+        <entry>Date/time value converted from a string using the specified template</entry>
+        <entry><literal>["12:30", "18:40"]</literal></entry>
+        <entry><literal>$[*].datetime("HH24:MI")</literal></entry>
+        <entry><literal>"12:30:00", "18:40:00"</literal></entry>
+       </row>
        <row>
         <entry><literal>keyvalue()</literal></entry>
         <entry>
@@ -11976,6 +11980,26 @@ table2-mapping
      </tgroup>
     </table>
 
+    <note>
+     <para>
+      The result type of <literal>datetime()</literal> and
+      <literal>datetime(<replaceable>template</replaceable>)</literal>
+      methods can be <type>date</type>, <type>timetz</type>, <type>time</type>,
+      <type>timestamptz</type>, or <type>timestamp</type>.
+      Both methods determine the result type dynamically.
+     </para>
+     <para>
+      The <literal>datetime()</literal> method sequentially tries ISO formats
+      for <type>date</type>, <type>timetz</type>, <type>time</type>,
+      <type>timestamptz</type>, and <type>timestamp</type>. It stops on
+      the first matching format and the corresponding data type.
+     </para>
+     <para>
+      The <literal>datetime(<replaceable>template</replaceable>)</literal>
+      method determines the result type by the provided template string.
+     </para>
+    </note>
+
     <table id="functions-sqljson-filter-ex-table">
      <title><type>jsonpath</type> Filter Expression Elements</title>
      <tgroup cols="5">
@@ -12118,6 +12142,15 @@ table2-mapping
       </tbody>
      </tgroup>
     </table>
+
+    <note>
+     <para>
+      When different date/time values are compared, an implicit cast is
+      applied. A <type>date</type> value can be cast to <type>timestamp</type>
+      or <type>timestamptz</type>, <type>timestamp</type> can be cast to
+      <type>timestamptz</type>, and <type>time</type> &mdash; to <type>timetz</type>.
+     </para>
+    </note>
    </sect3>
 
   </sect2>
@@ -12351,7 +12384,7 @@ table2-mapping
    <para>
     The <literal>@?</literal> and <literal>@@</literal> operators suppress
     the following errors: lacking object field or array element, unexpected
-    JSON item type, and numeric errors.
+    JSON item type, datetime and numeric errors.
     This behavior might be helpful while searching over JSON document
     collections of varying structure.
    </para>
@@ -12620,18 +12653,33 @@ table2-mapping
   <indexterm>
    <primary>jsonb_path_exists</primary>
   </indexterm>
+  <indexterm>
+   <primary>jsonb_path_exists_tz</primary>
+  </indexterm>
   <indexterm>
    <primary>jsonb_path_match</primary>
   </indexterm>
+  <indexterm>
+   <primary>jsonb_path_match_tz</primary>
+  </indexterm>
   <indexterm>
    <primary>jsonb_path_query</primary>
   </indexterm>
+  <indexterm>
+   <primary>jsonb_path_query_tz</primary>
+  </indexterm>
   <indexterm>
    <primary>jsonb_path_query_array</primary>
   </indexterm>
+  <indexterm>
+   <primary>jsonb_path_query_array_tz</primary>
+  </indexterm>
   <indexterm>
    <primary>jsonb_path_query_first</primary>
   </indexterm>
+  <indexterm>
+   <primary>jsonb_path_query_first_tz</primary>
+  </indexterm>
 
   <table id="functions-json-processing-table">
     <title>JSON Processing Functions</title>
@@ -12971,6 +13019,9 @@ table2-mapping
          <para><literal>
            jsonb_path_exists(target jsonb, path jsonpath [, vars jsonb, silent bool])
          </literal></para>
+         <para><literal>
+           jsonb_path_exists_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>boolean</type></entry>
         <entry>
@@ -12991,6 +13042,9 @@ table2-mapping
          <para><literal>
            jsonb_path_match(target jsonb, path jsonpath [, vars jsonb, silent bool])
          </literal></para>
+         <para><literal>
+           jsonb_path_match_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>boolean</type></entry>
         <entry>
@@ -13013,6 +13067,9 @@ table2-mapping
          <para><literal>
            jsonb_path_query(target jsonb, path jsonpath [, vars jsonb, silent bool])
          </literal></para>
+         <para><literal>
+           jsonb_path_query_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>setof jsonb</type></entry>
         <entry>
@@ -13041,6 +13098,9 @@ table2-mapping
          <para><literal>
            jsonb_path_query_array(target jsonb, path jsonpath [, vars jsonb, silent bool])
          </literal></para>
+         <para><literal>
+           jsonb_path_query_array_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>jsonb</type></entry>
         <entry>
@@ -13061,6 +13121,9 @@ table2-mapping
          <para><literal>
            jsonb_path_query_first(target jsonb, path jsonpath [, vars jsonb, silent bool])
          </literal></para>
+         <para><literal>
+           jsonb_path_query_first_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>jsonb</type></entry>
         <entry>
@@ -13203,11 +13266,8 @@ table2-mapping
 
   <note>
    <para>
-    The <literal>jsonb_path_exists</literal>, <literal>jsonb_path_match</literal>,
-    <literal>jsonb_path_query</literal>, <literal>jsonb_path_query_array</literal> and
-    <literal>jsonb_path_query_first</literal>
-    functions have optional <literal>vars</literal> and <literal>silent</literal>
-    arguments.
+    The <literal>jsonb_path_*</literal> functions have optional
+    <literal>vars</literal> and <literal>silent</literal> arguments.
    </para>
    <para>
     If the <literal>vars</literal> argument is specified, it provides an
@@ -13221,6 +13281,20 @@ table2-mapping
    </para>
   </note>
 
+  <note>
+   <para>
+    Some of the <literal>jsonb_path_*</literal> functions have the
+    <literal>_tz</literal> suffix. These functions have been implemented to
+    support comparison of date/time values that involves implicit
+    timezone-aware casts. Since operations with time zones are not immutable,
+    these functions are qualified as stable. Their counterparts without the
+    suffix do not support such casts, so they are immutable and can be used for
+    such use-cases as expression indexes
+    (see <xref linkend="indexes-expressional"/>). There is no difference
+    between these functions for other <type>jsonpath</type> operations.
+   </para>
+  </note>
+
   <para>
     See also <xref linkend="functions-aggregate"/> for the aggregate
     function <function>json_agg</function> which aggregates record
diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index ae874f38ee3..d373bf1d37b 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -544,7 +544,7 @@ T828	JSON_QUERY			NO
 T829	JSON_QUERY: array wrapper options			NO	
 T830	Enforcing unique keys in SQL/JSON constructor functions			NO	
 T831	SQL/JSON path language: strict mode			YES	
-T832	SQL/JSON path language: item method			NO	datetime() not yet implemented
+T832	SQL/JSON path language: item method			YES	
 T833	SQL/JSON path language: multiple subscripts			YES	
 T834	SQL/JSON path language: wildcard member accessor			YES	
 T835	SQL/JSON path language: filter expressions			YES	
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ea4c85e3959..423ae4fdc8a 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1287,6 +1287,46 @@ LANGUAGE INTERNAL
 STRICT IMMUTABLE PARALLEL SAFE
 AS 'jsonb_path_query_first';
 
+CREATE OR REPLACE FUNCTION
+  jsonb_path_exists_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                    silent boolean DEFAULT false)
+RETURNS boolean
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_exists_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_match_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                   silent boolean DEFAULT false)
+RETURNS boolean
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_match_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_query_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                   silent boolean DEFAULT false)
+RETURNS SETOF jsonb
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_query_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_query_array_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                         silent boolean DEFAULT false)
+RETURNS jsonb
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_query_array_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_query_first_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                         silent boolean DEFAULT false)
+RETURNS jsonb
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_query_first_tz';
+
 --
 -- The default permissions for functions mean that anyone can execute them.
 -- A number of functions shouldn't be executable by just anyone, but rather
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 87ae60e490f..65c047acc86 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -337,12 +337,14 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
 		case jpiPlus:
 		case jpiMinus:
 		case jpiExists:
+		case jpiDatetime:
 			{
 				int32		arg = reserveSpaceForItemPointer(buf);
 
-				chld = flattenJsonPathParseItem(buf, item->value.arg,
-												nestingLevel + argNestingLevel,
-												insideArraySubscript);
+				chld = !item->value.arg ? pos :
+					flattenJsonPathParseItem(buf, item->value.arg,
+											 nestingLevel + argNestingLevel,
+											 insideArraySubscript);
 				*(int32 *) (buf->data + arg) = chld - pos;
 			}
 			break;
@@ -692,6 +694,15 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
 		case jpiDouble:
 			appendBinaryStringInfo(buf, ".double()", 9);
 			break;
+		case jpiDatetime:
+			appendBinaryStringInfo(buf, ".datetime(", 10);
+			if (v->content.arg)
+			{
+				jspGetArg(v, &elem);
+				printJsonPathItem(buf, &elem, false, false);
+			}
+			appendStringInfoChar(buf, ')');
+			break;
 		case jpiKeyValue:
 			appendBinaryStringInfo(buf, ".keyvalue()", 11);
 			break;
@@ -754,6 +765,8 @@ jspOperationName(JsonPathItemType type)
 			return "floor";
 		case jpiCeiling:
 			return "ceiling";
+		case jpiDatetime:
+			return "datetime";
 		default:
 			elog(ERROR, "unrecognized jsonpath item type: %d", type);
 			return NULL;
@@ -889,6 +902,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
 		case jpiPlus:
 		case jpiMinus:
 		case jpiFilter:
+		case jpiDatetime:
 			read_int32(v->content.arg, base, pos);
 			break;
 		case jpiIndexArray:
@@ -913,7 +927,8 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a)
 		   v->type == jpiIsUnknown ||
 		   v->type == jpiExists ||
 		   v->type == jpiPlus ||
-		   v->type == jpiMinus);
+		   v->type == jpiMinus ||
+		   v->type == jpiDatetime);
 
 	jspInitByBuffer(a, v->base, v->content.arg);
 }
@@ -961,6 +976,7 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
 			   v->type == jpiFloor ||
 			   v->type == jpiCeiling ||
 			   v->type == jpiDouble ||
+			   v->type == jpiDatetime ||
 			   v->type == jpiKeyValue ||
 			   v->type == jpiStartsWith);
 
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index d8647f71af3..6284da298a2 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -66,6 +66,7 @@
 #include "miscadmin.h"
 #include "regex/regex.h"
 #include "utils/builtins.h"
+#include "utils/datetime.h"
 #include "utils/datum.h"
 #include "utils/formatting.h"
 #include "utils/float.h"
@@ -107,6 +108,7 @@ typedef struct JsonPathExecContext
 										 * ignored */
 	bool		throwErrors;	/* with "false" all suppressible errors are
 								 * suppressed */
+	bool		useTz;
 } JsonPathExecContext;
 
 /* Context for LIKE_REGEX execution. */
@@ -173,7 +175,8 @@ typedef JsonPathBool (*JsonPathPredicateCallback) (JsonPathItem *jsp,
 typedef Numeric (*BinaryArithmFunc) (Numeric num1, Numeric num2, bool *error);
 
 static JsonPathExecResult executeJsonPath(JsonPath *path, Jsonb *vars,
-										  Jsonb *json, bool throwErrors, JsonValueList *result);
+										  Jsonb *json, bool throwErrors,
+										  JsonValueList *result, bool useTz);
 static JsonPathExecResult executeItem(JsonPathExecContext *cxt,
 									  JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
 static JsonPathExecResult executeItemOptUnwrapTarget(JsonPathExecContext *cxt,
@@ -216,6 +219,8 @@ static JsonPathBool executeLikeRegex(JsonPathItem *jsp, JsonbValue *str,
 static JsonPathExecResult executeNumericItemMethod(JsonPathExecContext *cxt,
 												   JsonPathItem *jsp, JsonbValue *jb, bool unwrap, PGFunction func,
 												   JsonValueList *found);
+static JsonPathExecResult executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
+												JsonbValue *jb, JsonValueList *found);
 static JsonPathExecResult executeKeyValueMethod(JsonPathExecContext *cxt,
 												JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
 static JsonPathExecResult appendBoolResult(JsonPathExecContext *cxt,
@@ -227,7 +232,8 @@ static void getJsonPathVariable(JsonPathExecContext *cxt,
 static int	JsonbArraySize(JsonbValue *jb);
 static JsonPathBool executeComparison(JsonPathItem *cmp, JsonbValue *lv,
 									  JsonbValue *rv, void *p);
-static JsonPathBool compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2);
+static JsonPathBool compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2,
+								 bool useTz);
 static int	compareNumeric(Numeric a, Numeric b);
 static JsonbValue *copyJsonbValue(JsonbValue *src);
 static JsonPathExecResult getArrayIndex(JsonPathExecContext *cxt,
@@ -248,6 +254,8 @@ static JsonbValue *JsonbInitBinary(JsonbValue *jbv, Jsonb *jb);
 static int	JsonbType(JsonbValue *jb);
 static JsonbValue *getScalar(JsonbValue *scalar, enum jbvType type);
 static JsonbValue *wrapItemsInArray(const JsonValueList *items);
+static int	compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
+							bool useTz, bool *have_error);
 
 /****************** User interface to JsonPath executor ********************/
 
@@ -263,8 +271,8 @@ static JsonbValue *wrapItemsInArray(const JsonValueList *items);
  *		SQL/JSON.  Regarding jsonb_path_match(), this function doesn't have
  *		an analogy in SQL/JSON, so we define its behavior on our own.
  */
-Datum
-jsonb_path_exists(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_exists_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -278,7 +286,7 @@ jsonb_path_exists(PG_FUNCTION_ARGS)
 		silent = PG_GETARG_BOOL(3);
 	}
 
-	res = executeJsonPath(jp, vars, jb, !silent, NULL);
+	res = executeJsonPath(jp, vars, jb, !silent, NULL, tz);
 
 	PG_FREE_IF_COPY(jb, 0);
 	PG_FREE_IF_COPY(jp, 1);
@@ -289,6 +297,18 @@ jsonb_path_exists(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(res == jperOk);
 }
 
+Datum
+jsonb_path_exists(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_exists_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_exists_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_exists_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_exists_opr
  *		Implementation of operator "jsonb @? jsonpath" (2-argument version of
@@ -298,7 +318,7 @@ Datum
 jsonb_path_exists_opr(PG_FUNCTION_ARGS)
 {
 	/* just call the other one -- it can handle both cases */
-	return jsonb_path_exists(fcinfo);
+	return jsonb_path_exists_internal(fcinfo, false);
 }
 
 /*
@@ -306,8 +326,8 @@ jsonb_path_exists_opr(PG_FUNCTION_ARGS)
  *		Returns jsonpath predicate result item for the specified jsonb value.
  *		See jsonb_path_exists() comment for details regarding error handling.
  */
-Datum
-jsonb_path_match(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_match_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -321,7 +341,7 @@ jsonb_path_match(PG_FUNCTION_ARGS)
 		silent = PG_GETARG_BOOL(3);
 	}
 
-	(void) executeJsonPath(jp, vars, jb, !silent, &found);
+	(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 	PG_FREE_IF_COPY(jb, 0);
 	PG_FREE_IF_COPY(jp, 1);
@@ -345,6 +365,18 @@ jsonb_path_match(PG_FUNCTION_ARGS)
 	PG_RETURN_NULL();
 }
 
+Datum
+jsonb_path_match(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_match_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_match_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_match_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_match_opr
  *		Implementation of operator "jsonb @@ jsonpath" (2-argument version of
@@ -354,7 +386,7 @@ Datum
 jsonb_path_match_opr(PG_FUNCTION_ARGS)
 {
 	/* just call the other one -- it can handle both cases */
-	return jsonb_path_match(fcinfo);
+	return jsonb_path_match_internal(fcinfo, false);
 }
 
 /*
@@ -362,8 +394,8 @@ jsonb_path_match_opr(PG_FUNCTION_ARGS)
  *		Executes jsonpath for given jsonb document and returns result as
  *		rowset.
  */
-Datum
-jsonb_path_query(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_query_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	FuncCallContext *funcctx;
 	List	   *found;
@@ -387,7 +419,7 @@ jsonb_path_query(PG_FUNCTION_ARGS)
 		vars = PG_GETARG_JSONB_P_COPY(2);
 		silent = PG_GETARG_BOOL(3);
 
-		(void) executeJsonPath(jp, vars, jb, !silent, &found);
+		(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 		funcctx->user_fctx = JsonValueListGetList(&found);
 
@@ -408,13 +440,25 @@ jsonb_path_query(PG_FUNCTION_ARGS)
 	SRF_RETURN_NEXT(funcctx, JsonbPGetDatum(JsonbValueToJsonb(v)));
 }
 
+Datum
+jsonb_path_query(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_query_array
  *		Executes jsonpath for given jsonb document and returns result as
  *		jsonb array.
  */
-Datum
-jsonb_path_query_array(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_query_array_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -422,18 +466,30 @@ jsonb_path_query_array(PG_FUNCTION_ARGS)
 	Jsonb	   *vars = PG_GETARG_JSONB_P(2);
 	bool		silent = PG_GETARG_BOOL(3);
 
-	(void) executeJsonPath(jp, vars, jb, !silent, &found);
+	(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 	PG_RETURN_JSONB_P(JsonbValueToJsonb(wrapItemsInArray(&found)));
 }
 
+Datum
+jsonb_path_query_array(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_array_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_array_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_array_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_query_first
  *		Executes jsonpath for given jsonb document and returns first result
  *		item.  If there are no items, NULL returned.
  */
-Datum
-jsonb_path_query_first(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_query_first_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -441,7 +497,7 @@ jsonb_path_query_first(PG_FUNCTION_ARGS)
 	Jsonb	   *vars = PG_GETARG_JSONB_P(2);
 	bool		silent = PG_GETARG_BOOL(3);
 
-	(void) executeJsonPath(jp, vars, jb, !silent, &found);
+	(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 	if (JsonValueListLength(&found) >= 1)
 		PG_RETURN_JSONB_P(JsonbValueToJsonb(JsonValueListHead(&found)));
@@ -449,6 +505,18 @@ jsonb_path_query_first(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 }
 
+Datum
+jsonb_path_query_first(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_first_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_first_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_first_internal(fcinfo, true);
+}
+
 /********************Execute functions for JsonPath**************************/
 
 /*
@@ -472,7 +540,7 @@ jsonb_path_query_first(PG_FUNCTION_ARGS)
  */
 static JsonPathExecResult
 executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors,
-				JsonValueList *result)
+				JsonValueList *result, bool useTz)
 {
 	JsonPathExecContext cxt;
 	JsonPathExecResult res;
@@ -502,6 +570,7 @@ executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors,
 	cxt.lastGeneratedObjectId = vars ? 2 : 1;
 	cxt.innermostArraySize = -1;
 	cxt.throwErrors = throwErrors;
+	cxt.useTz = useTz;
 
 	if (jspStrictAbsenseOfErrors(&cxt) && !result)
 	{
@@ -1030,6 +1099,12 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
 			}
 			break;
 
+		case jpiDatetime:
+			if (unwrap && JsonbType(jb) == jbvArray)
+				return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
+
+			return executeDateTimeMethod(cxt, jsp, jb, found);
+
 		case jpiKeyValue:
 			if (unwrap && JsonbType(jb) == jbvArray)
 				return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
@@ -1216,7 +1291,7 @@ executeBoolItem(JsonPathExecContext *cxt, JsonPathItem *jsp,
 			jspGetLeftArg(jsp, &larg);
 			jspGetRightArg(jsp, &rarg);
 			return executePredicate(cxt, jsp, &larg, &rarg, jb, true,
-									executeComparison, NULL);
+									executeComparison, cxt);
 
 		case jpiStartsWith:		/* 'whole STARTS WITH initial' */
 			jspGetLeftArg(jsp, &larg);	/* 'whole' */
@@ -1719,6 +1794,138 @@ executeNumericItemMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
 	return executeNextItem(cxt, jsp, &next, jb, found, false);
 }
 
+/*
+ * Implementation of the .datetime() method.
+ *
+ * Converts a string into a date/time value. The actual type is determined at run time.
+ * If an argument is provided, this argument is used as a template string.
+ * Otherwise, the first fitting ISO format is selected.
+ */
+static JsonPathExecResult
+executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
+					  JsonbValue *jb, JsonValueList *found)
+{
+	JsonbValue	jbvbuf;
+	Datum		value;
+	text	   *datetime;
+	Oid			typid;
+	int32		typmod = -1;
+	int			tz = 0;
+	bool		hasNext;
+	JsonPathExecResult res = jperNotFound;
+	JsonPathItem elem;
+
+	if (!(jb = getScalar(jb, jbvString)))
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+							  errmsg("invalid argument for SQL/JSON datetime function"),
+							  errdetail("jsonpath item method .%s() can only be applied to a string",
+										jspOperationName(jsp->type)))));
+
+	datetime = cstring_to_text_with_len(jb->val.string.val,
+										jb->val.string.len);
+
+	if (jsp->content.arg)
+	{
+		text	   *template;
+		char	   *template_str;
+		int			template_len;
+		bool		have_error = false;
+
+		jspGetArg(jsp, &elem);
+
+		if (elem.type != jpiString)
+			elog(ERROR, "invalid jsonpath item type for .datetime() argument");
+
+		template_str = jspGetString(&elem, &template_len);
+
+		template = cstring_to_text_with_len(template_str,
+											template_len);
+
+		value = parse_datetime(datetime, template, true,
+							   &typid, &typmod, &tz,
+							   jspThrowErrors(cxt) ? NULL : &have_error);
+
+		if (have_error)
+			res = jperError;
+		else
+			res = jperOk;
+	}
+	else
+	{
+		/*
+		 * According to SQL/JSON standard enumerate ISO formats for: date,
+		 * timetz, time, timestamptz, timestamp.
+		 */
+		static const char *fmt_str[] =
+		{
+			"yyyy-mm-dd",
+			"HH24:MI:SS TZH:TZM",
+			"HH24:MI:SS TZH",
+			"HH24:MI:SS",
+			"yyyy-mm-dd HH24:MI:SS TZH:TZM",
+			"yyyy-mm-dd HH24:MI:SS TZH",
+			"yyyy-mm-dd HH24:MI:SS"
+		};
+
+		/* cache for format texts */
+		static text *fmt_txt[lengthof(fmt_str)] = {0};
+		int			i;
+
+		/* loop until datetime format fits */
+		for (i = 0; i < lengthof(fmt_str); i++)
+		{
+			bool		have_error = false;
+
+			if (!fmt_txt[i])
+			{
+				MemoryContext oldcxt =
+				MemoryContextSwitchTo(TopMemoryContext);
+
+				fmt_txt[i] = cstring_to_text(fmt_str[i]);
+				MemoryContextSwitchTo(oldcxt);
+			}
+
+			value = parse_datetime(datetime, fmt_txt[i], true,
+								   &typid, &typmod, &tz,
+								   &have_error);
+
+			if (!have_error)
+			{
+				res = jperOk;
+				break;
+			}
+		}
+
+		if (res == jperNotFound)
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+								  errmsg("invalid argument for SQL/JSON datetime function"),
+								  errdetail("unrecognized datetime format"),
+								  errhint("use datetime template argument for explicit format specification"))));
+	}
+
+	pfree(datetime);
+
+	if (jperIsError(res))
+		return res;
+
+	hasNext = jspGetNext(jsp, &elem);
+
+	if (!hasNext && !found)
+		return res;
+
+	jb = hasNext ? &jbvbuf : palloc(sizeof(*jb));
+
+	jb->type = jbvDatetime;
+	jb->val.datetime.value = value;
+	jb->val.datetime.typid = typid;
+	jb->val.datetime.typmod = typmod;
+	jb->val.datetime.tz = tz;
+
+	return executeNextItem(cxt, jsp, &elem, jb, found, hasNext);
+}
+
 /*
  * Implementation of .keyvalue() method.
  *
@@ -1979,14 +2186,16 @@ JsonbArraySize(JsonbValue *jb)
 static JsonPathBool
 executeComparison(JsonPathItem *cmp, JsonbValue *lv, JsonbValue *rv, void *p)
 {
-	return compareItems(cmp->type, lv, rv);
+	JsonPathExecContext *cxt = (JsonPathExecContext *) p;
+
+	return compareItems(cmp->type, lv, rv, cxt->useTz);
 }
 
 /*
  * Compare two SQL/JSON items using comparison operation 'op'.
  */
 static JsonPathBool
-compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2)
+compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2, bool useTz)
 {
 	int			cmp;
 	bool		res;
@@ -2028,6 +2237,21 @@ compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2)
 							 jb2->val.string.val, jb2->val.string.len,
 							 DEFAULT_COLLATION_OID);
 			break;
+		case jbvDatetime:
+			{
+				bool		have_error = false;
+
+				cmp = compareDatetime(jb1->val.datetime.value,
+									  jb1->val.datetime.typid,
+									  jb2->val.datetime.value,
+									  jb2->val.datetime.typid,
+									  useTz,
+									  &have_error);
+
+				if (have_error)
+					return jpbUnknown;
+			}
+			break;
 
 		case jbvBinary:
 		case jbvArray:
@@ -2287,3 +2511,205 @@ wrapItemsInArray(const JsonValueList *items)
 
 	return pushJsonbValue(&ps, WJB_END_ARRAY, NULL);
 }
+
+/*
+ * Cross-type comparison of two datetime SQL/JSON items.  If items are
+ * uncomparable, 'error' flag is set.
+ */
+static int
+compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
+				bool useTz, bool *have_error)
+{
+	PGFunction cmpfunc = NULL;
+
+	switch (typid1)
+	{
+		case DATEOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					cmpfunc = date_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					val1 = date2timestamp_opt_error(val1, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"date", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val1 = date2timestamptz_opt_error(val1, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMEOID:
+			switch (typid2)
+			{
+				case TIMEOID:
+					cmpfunc = time_cmp;
+
+					break;
+
+				case TIMETZOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"time", "timetz"),
+								 errhint("use *_tz() function for timezone support")));
+					val1 = DirectFunctionCall1(time_timetz, val1);
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case DATEOID:
+				case TIMESTAMPOID:
+				case TIMESTAMPTZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMETZOID:
+			switch (typid2)
+			{
+				case TIMEOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"time", "timetz"),
+								 errhint("use *_tz() function for timezone support")));
+					val2 = DirectFunctionCall1(time_timetz, val2);
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case TIMETZOID:
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case DATEOID:
+				case TIMESTAMPOID:
+				case TIMESTAMPTZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMESTAMPOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					val2 = date2timestamp_opt_error(val2, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"timestamp", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val1 = timestamp2timestamptz_opt_error(val1, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMESTAMPTZOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"date", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val2 = date2timestamptz_opt_error(val2, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"timestamp", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val2 = timestamp2timestamptz_opt_error(val2, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		default:
+			elog(ERROR, "unrecognized SQL/JSON datetime type oid: %d",
+				 typid1);
+	}
+
+	if (*have_error)
+		return 0;
+
+	if (!cmpfunc)
+		elog(ERROR, "unrecognized SQL/JSON datetime type oid: %d",
+			 typid2);
+
+	*have_error = false;
+
+	return DatumGetInt32(DirectFunctionCall2(cmpfunc, val1, val2));
+}
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
index 91b4b2f5985..059faeeb2c7 100644
--- a/src/backend/utils/adt/jsonpath_gram.y
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -94,12 +94,14 @@ static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr,
 %token	<str>		LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
 %token	<str>		ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P
 %token	<str>		ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P KEYVALUE_P
+%token	<str>		DATETIME_P
 
 %type	<result>	result
 
 %type	<value>		scalar_value path_primary expr array_accessor
 					any_path accessor_op key predicate delimited_predicate
 					index_elem starts_with_initial expr_or_predicate
+					datetime_template opt_datetime_template
 
 %type	<elems>		accessor_expr
 
@@ -247,9 +249,20 @@ accessor_op:
 	| array_accessor				{ $$ = $1; }
 	| '.' any_path					{ $$ = $2; }
 	| '.' method '(' ')'			{ $$ = makeItemType($2); }
+	| '.' DATETIME_P '(' opt_datetime_template ')'
+									{ $$ = makeItemUnary(jpiDatetime, $4); }
 	| '?' '(' predicate ')'			{ $$ = makeItemUnary(jpiFilter, $3); }
 	;
 
+datetime_template:
+	STRING_P						{ $$ = makeItemString(&$1); }
+	;
+
+opt_datetime_template:
+	datetime_template				{ $$ = $1; }
+	| /* EMPTY */					{ $$ = NULL; }
+	;
+
 key:
 	key_name						{ $$ = makeItemKey(&$1); }
 	;
@@ -272,6 +285,7 @@ key_name:
 	| FLOOR_P
 	| DOUBLE_P
 	| CEILING_P
+	| DATETIME_P
 	| KEYVALUE_P
 	| LAST_P
 	| STARTS_P
diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l
index 2165ffcc254..ced65edb59b 100644
--- a/src/backend/utils/adt/jsonpath_scan.l
+++ b/src/backend/utils/adt/jsonpath_scan.l
@@ -340,6 +340,7 @@ static const JsonPathKeyword keywords[] = {
 	{ 6, false,	STRICT_P,	"strict"},
 	{ 7, false,	CEILING_P,	"ceiling"},
 	{ 7, false,	UNKNOWN_P,	"unknown"},
+	{ 8, false,	DATETIME_P,	"datetime"},
 	{ 8, false,	KEYVALUE_P,	"keyvalue"},
 	{ 10,false, LIKE_REGEX_P, "like_regex"},
 };
diff --git a/src/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index 16f5ca233a9..72876533acf 100644
--- a/src/backend/utils/errcodes.txt
+++ b/src/backend/utils/errcodes.txt
@@ -207,6 +207,7 @@ Section: Class 22 - Data Exception
 2200S    E    ERRCODE_INVALID_XML_COMMENT                                    invalid_xml_comment
 2200T    E    ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION                     invalid_xml_processing_instruction
 22030    E    ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE                        duplicate_json_object_key_value
+22031    E    ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION            invalid_argument_for_json_datetime_function
 22032    E    ERRCODE_INVALID_JSON_TEXT                                      invalid_json_text
 22033    E    ERRCODE_INVALID_JSON_SUBSCRIPT                                 invalid_json_subscript
 22034    E    ERRCODE_MORE_THAN_ONE_JSON_ITEM                                more_than_one_json_item
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0902dce5f14..4acdd7a3956 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9323,6 +9323,28 @@
   proname => 'jsonb_path_match', prorettype => 'bool',
   proargtypes => 'jsonb jsonpath jsonb bool', prosrc => 'jsonb_path_match' },
 
+{ oid => '6015', descr => 'jsonpath exists test with timezone',
+  proname => 'jsonb_path_exists_tz', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_exists_tz' },
+{ oid => '6016', descr => 'jsonpath query with timezone',
+  proname => 'jsonb_path_query_tz', provolatile => 's',
+  prorows => '1000', proretset => 't',
+  prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_query_tz' },
+{ oid => '6017', descr => 'jsonpath query wrapped into array with timezone',
+  proname => 'jsonb_path_query_array_tz', provolatile => 's',
+  prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_query_array_tz' },
+{ oid => '6018', descr => 'jsonpath query first item with timezone',
+  proname => 'jsonb_path_query_first_tz', provolatile => 's',
+  prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_query_first_tz' },
+{ oid => '6019', descr => 'jsonpath match with timezone',
+  proname => 'jsonb_path_match_tz', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_match_tz' },
+
 { oid => '4010', descr => 'implementation of @? operator',
   proname => 'jsonb_path_exists_opr', prorettype => 'bool',
   proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_path_exists_opr' },
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 40ad5fda928..f6b17c8aa2f 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -79,6 +79,7 @@ typedef enum JsonPathItemType
 	jpiFloor,					/* .floor() item method */
 	jpiCeiling,					/* .ceiling() item method */
 	jpiDouble,					/* .double() item method */
+	jpiDatetime,				/* .datetime() item method */
 	jpiKeyValue,				/* .keyvalue() item method */
 	jpiSubscript,				/* array subscript: 'expr' or 'expr TO expr' */
 	jpiLast,					/* LAST array subscript */
diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
index 31a871af028..ac668a6860a 100644
--- a/src/test/regress/expected/jsonb_jsonpath.out
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -1658,6 +1658,532 @@ select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ lik
  "a\b"
 (1 row)
 
+select jsonb_path_query('null', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('true', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('1', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('[]', '$.datetime()');
+ jsonb_path_query 
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'strict $.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('{}', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('""', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  unrecognized datetime format
+HINT:  use datetime template argument for explicit format specification
+select jsonb_path_query('"12:34"', '$.datetime("aaa")');
+ERROR:  trailing characters remain in input string after datetime format
+select jsonb_path_query('"aaaa"', '$.datetime("HH24")');
+ERROR:  invalid value "aa" for "HH24"
+DETAIL:  Value must be an integer.
+select jsonb '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")';
+ ?column? 
+----------
+ t
+(1 row)
+
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+ jsonb_path_query 
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+ jsonb_path_query 
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+ERROR:  trailing characters remain in input string after datetime format
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+ERROR:  trailing characters remain in input string after datetime format
+select jsonb_path_query('"10-03-2017 12:34"', '       $.datetime("dd-mm-yyyy HH24:MI").type()');
+       jsonb_path_query        
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+      jsonb_path_query      
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+     jsonb_path_query     
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+   jsonb_path_query    
+-----------------------
+ "time with time zone"
+(1 row)
+
+set time zone '+00';
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+ jsonb_path_query 
+------------------
+ "12:34:00"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone '+10';
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+ jsonb_path_query 
+------------------
+ "12:34:00"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone default;
+select jsonb_path_query('"2017-03-10"', '$.datetime().type()');
+ jsonb_path_query 
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"2017-03-10"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+       jsonb_path_query        
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:34:56"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()');
+      jsonb_path_query      
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:56+03:00"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()');
+      jsonb_path_query      
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:56+03:10"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime().type()');
+     jsonb_path_query     
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "12:34:56"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()');
+   jsonb_path_query    
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "12:34:56+03:00"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()');
+   jsonb_path_query    
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "12:34:56+03:10"
+(1 row)
+
+set time zone '+00';
+-- date comparison
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR:  cannot convert value from date to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR:  cannot convert value from date to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR:  cannot convert value from date to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10"
+ "2017-03-10T00:00:00"
+ "2017-03-10T03:00:00+03:00"
+(3 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10"
+ "2017-03-11"
+ "2017-03-10T00:00:00"
+ "2017-03-10T12:34:56"
+ "2017-03-10T03:00:00+03:00"
+(5 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-09"
+ "2017-03-10T01:02:03+04:00"
+(2 rows)
+
+-- time comparison
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00"
+ "12:35:00+00:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00"
+ "12:36:00"
+ "12:35:00+00:00"
+(3 rows)
+
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:34:00"
+ "12:35:00+01:00"
+ "13:35:00+01:00"
+(3 rows)
+
+-- timetz comparison
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00+01:00"
+(1 row)
+
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00+01:00"
+ "12:36:00+01:00"
+ "12:35:00-02:00"
+ "11:35:00"
+ "12:35:00"
+(5 rows)
+
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:34:00+01:00"
+ "12:35:00+02:00"
+ "10:35:00"
+(3 rows)
+
+-- timestamp comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T13:35:00+01:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T12:36:00"
+ "2017-03-10T13:35:00+01:00"
+ "2017-03-10T12:35:00-01:00"
+ "2017-03-11"
+(5 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:34:00"
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10"
+(3 rows)
+
+-- timestamptz comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T11:35:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+set time zone default;
 -- jsonpath operators
 SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
  jsonb_path_query 
diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
index ecdd453942b..f5d5fa4fb8d 100644
--- a/src/test/regress/expected/jsonpath.out
+++ b/src/test/regress/expected/jsonpath.out
@@ -401,6 +401,18 @@ select '$.keyvalue().key'::jsonpath;
  $.keyvalue()."key"
 (1 row)
 
+select '$.datetime()'::jsonpath;
+   jsonpath   
+--------------
+ $.datetime()
+(1 row)
+
+select '$.datetime("datetime template")'::jsonpath;
+            jsonpath             
+---------------------------------
+ $.datetime("datetime template")
+(1 row)
+
 select '$ ? (@ starts with "abc")'::jsonpath;
         jsonpath         
 -------------------------
diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
index 733fbd4e0d0..2c16182e554 100644
--- a/src/test/regress/sql/jsonb_jsonpath.sql
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -346,6 +346,178 @@ select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ lik
 select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\B$" flag "iq")');
 select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\b$" flag "")');
 
+select jsonb_path_query('null', '$.datetime()');
+select jsonb_path_query('true', '$.datetime()');
+select jsonb_path_query('1', '$.datetime()');
+select jsonb_path_query('[]', '$.datetime()');
+select jsonb_path_query('[]', 'strict $.datetime()');
+select jsonb_path_query('{}', '$.datetime()');
+select jsonb_path_query('""', '$.datetime()');
+select jsonb_path_query('"12:34"', '$.datetime("aaa")');
+select jsonb_path_query('"aaaa"', '$.datetime("HH24")');
+
+select jsonb '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")';
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+
+select jsonb_path_query('"10-03-2017 12:34"', '       $.datetime("dd-mm-yyyy HH24:MI").type()');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+
+set time zone '+00';
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone '+10';
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone default;
+
+select jsonb_path_query('"2017-03-10"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()');
+select jsonb_path_query('"12:34:56"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56"', '$.datetime()');
+select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56 +3"', '$.datetime()');
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()');
+
+set time zone '+00';
+
+-- date comparison
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+
+-- time comparison
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+
+-- timetz comparison
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+
+-- timestamp comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+
+-- timestamptz comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+
+set time zone default;
+
 -- jsonpath operators
 
 SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
index 29ea77a4858..713d32400a7 100644
--- a/src/test/regress/sql/jsonpath.sql
+++ b/src/test/regress/sql/jsonpath.sql
@@ -72,6 +72,8 @@ select '"aaa".type()'::jsonpath;
 select 'true.type()'::jsonpath;
 select '$.double().floor().ceiling().abs()'::jsonpath;
 select '$.keyvalue().key'::jsonpath;
+select '$.datetime()'::jsonpath;
+select '$.datetime("datetime template")'::jsonpath;
 
 select '$ ? (@ starts with "abc")'::jsonpath;
 select '$ ? (@ starts with $var)'::jsonpath;
#8Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Alexander Korotkov (#7)
Re: Support for jsonpath .datetime() method

I think the best way forward here is to focus first on patch 0002 and
get the additional format templates in, independent of any surrounding
JSON functionality.

In particular, remove parse_datetime() and all the related API changes,
then it becomes much simpler.

The codes FF1..FF6 that you added appear to be correct, but reading the
spec I find there is more missing, specifically

- RRRR and RR
- SSSSS (currently only SSSS is supported, but that's not standard)

Also in some cases we allow timestamps with seven digits of fractional
precision, so perhaps FF7 should be supported as well. I'm not quite
sure about the details here. You tests only cover 6 and 9 digits. It
would be good to cover 7 and perhaps 8 as well, since those are the
boundary cases.

Some concrete pieces of review:

+       <row>
+        <entry><literal>FF1</literal></entry>
+        <entry>decisecond (0-9)</entry>
+       </row>

Let's not use such weird terms as "deciseconds". We could say
"fractional seconds, 1 digit" etc. or something like that.

+/* Return flags for DCH_from_char() */
+#define DCH_DATED  0x01
+#define DCH_TIMED  0x02
+#define DCH_ZONED  0x04

I think you mean do_to_timestamp() here. These terms "dated" etc. are
from the SQL standard text, but they should be explained somewhere for
the readers of the code.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#9Nikita Glukhov
n.gluhov@postgrespro.ru
In reply to: Peter Eisentraut (#8)
Re: Support for jsonpath .datetime() method

On 23.07.2019 16:44, Peter Eisentraut wrote:

I think the best way forward here is to focus first on patch 0002 and
get the additional format templates in, independent of any surrounding
JSON functionality.

In particular, remove parse_datetime() and all the related API changes,
then it becomes much simpler.

The codes FF1..FF6 that you added appear to be correct, but reading the
spec I find there is more missing, specifically

- RRRR and RR

It seems that our YY works like RR should:

SELECT to_date('69', 'YY');
to_date
------------
2069-01-01
(1 row)

SELECT to_date('70', 'YY');
to_date
------------
1970-01-01
(1 row)

But by the standard first two digits of current year should be used in YY.

Oracle follows the standard but its implementation has the different
rounding algorithm:

SELECT TO_CHAR(TO_DATE('99', 'YY'), 'YYYY') from dual;
2099

SELECT TO_CHAR(TO_DATE('49', 'RR'), 'YYYY') from dual;
2049

SELECT TO_CHAR(TO_DATE('50', 'RR'), 'YYYY') from dual;
1950

So it's unclear what we should do:
- implement YY and RR strictly following the standard only in .datetime()
- fix YY implementation in to_date()/to_timestamp() and implement RR
- use our non-standard templates in .datetime()

- SSSSS (currently only SSSS is supported, but that's not standard)

SSSSS template can be easily added as alias to SSSS.

Also in some cases we allow timestamps with seven digits of fractional
precision, so perhaps FF7 should be supported as well. I'm not quite
sure about the details here. You tests only cover 6 and 9 digits. It
would be good to cover 7 and perhaps 8 as well, since those are the
boundary cases.

FF7-FF9 weer present in earlier versions of the jsonpath patches, but they
had been removed (see [1]/messages/by-id/885de241-5a51-29c8-a6b3-f1dda22aba13@postgrespro.ru) because they were not completely supported due
to the limited precision of timestamp.

Some concrete pieces of review:

+       <row>
+        <entry><literal>FF1</literal></entry>
+        <entry>decisecond (0-9)</entry>
+       </row>

Let's not use such weird terms as "deciseconds". We could say
"fractional seconds, 1 digit" etc. or something like that.

And what about "tenths of seconds", "hundredths of seconds"?

+/* Return flags for DCH_from_char() */
+#define DCH_DATED  0x01
+#define DCH_TIMED  0x02
+#define DCH_ZONED  0x04

I think you mean do_to_timestamp() here. These terms "dated" etc. are
from the SQL standard text, but they should be explained somewhere for
the readers of the code.

[1]: /messages/by-id/885de241-5a51-29c8-a6b3-f1dda22aba13@postgrespro.ru
/messages/by-id/885de241-5a51-29c8-a6b3-f1dda22aba13@postgrespro.ru

--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#10Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Nikita Glukhov (#9)
Re: Support for jsonpath .datetime() method

On Wed, Jul 24, 2019 at 1:50 AM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:

So it's unclear what we should do:
- implement YY and RR strictly following the standard only in .datetime()
- fix YY implementation in to_date()/to_timestamp() and implement RR
- use our non-standard templates in .datetime()

Also it appears that according to standard .datetime() should treat
spaces and delimiters differently than our to_date()/to_timestamp().
It requires strict matching of spaces and delimiters in input and
format strings. We don't have such behavior in both non-FX and FX
modes. Also, standard doesn't define FX mode at all. This rules
cover jsonpath .datetime() method and CAST(... FORMAT ...) – new cast
clause defined by standard.

So, I think due to reasons of compatibility it doesn't worth trying to
make behavior of our to_date()/to_timestamp() to fit requirements for
jsonpath .datetime() and CAST(... FORMAT ...). I propose to leave
this functions as is (maybe add new patterns), but introduce another
datetime parsing mode, which would fit to the standard. Opinions?

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#11Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Nikita Glukhov (#9)
Re: Support for jsonpath .datetime() method

On 2019-07-24 00:48, Nikita Glukhov wrote:

It seems that our YY works like RR should:

SELECT to_date('69', 'YY');
to_date
------------
2069-01-01
(1 row)

SELECT to_date('70', 'YY');
to_date
------------
1970-01-01
(1 row)

But by the standard first two digits of current year should be used in YY.

Is this behavior even documented anywhere in our documentation? I
couldn't find it. What's the exact specification of what it does in
these cases?

So it's unclear what we should do:
- implement YY and RR strictly following the standard only in .datetime()
- fix YY implementation in to_date()/to_timestamp() and implement RR
- use our non-standard templates in .datetime()

I think we definitely should try to use the same template system in both
the general functions and in .datetime(). This might involve some
compromises between existing behavior, Oracle behavior, SQL standard.
So far I'm not worried: If you're using two-digit years like above,
you're playing with fire anyway. Also some of the other cases like
dealing with trailing spaces are probably acceptable as slight
incompatibilities or extensions.

We should collect a list of test cases that illustrate the differences
and then work out how to deal with them.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#12Andrew Dunstan
andrew.dunstan@2ndquadrant.com
In reply to: Peter Eisentraut (#11)
Re: Support for jsonpath .datetime() method

On 7/24/19 4:25 PM, Peter Eisentraut wrote:

On 2019-07-24 00:48, Nikita Glukhov wrote:

It seems that our YY works like RR should:

SELECT to_date('69', 'YY');
to_date
------------
2069-01-01
(1 row)

SELECT to_date('70', 'YY');
to_date
------------
1970-01-01
(1 row)

But by the standard first two digits of current year should be used in YY.

Is this behavior even documented anywhere in our documentation? I
couldn't find it. What's the exact specification of what it does in
these cases?

So it's unclear what we should do:
- implement YY and RR strictly following the standard only in .datetime()
- fix YY implementation in to_date()/to_timestamp() and implement RR
- use our non-standard templates in .datetime()

I think we definitely should try to use the same template system in both
the general functions and in .datetime().

Agreed. It's too hard to maintain otherwise.

This might involve some
compromises between existing behavior, Oracle behavior, SQL standard.
So far I'm not worried: If you're using two-digit years like above,
you're playing with fire anyway. Also some of the other cases like
dealing with trailing spaces are probably acceptable as slight
incompatibilities or extensions.

My instict wouyld be to move as close as possible to the standard,
especially if the current behaviour isn't documented.

We should collect a list of test cases that illustrate the differences
and then work out how to deal with them.

Agreed.

cheers

andrew

--
Andrew Dunstan https://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#13Andrew Dunstan
andrew.dunstan@2ndquadrant.com
In reply to: Nikita Glukhov (#9)
Re: Support for jsonpath .datetime() method

On 7/23/19 6:48 PM, Nikita Glukhov wrote:

Some concrete pieces of review:

+       <row>
+        <entry><literal>FF1</literal></entry>
+        <entry>decisecond (0-9)</entry>
+       </row>

Let's not use such weird terms as "deciseconds". We could say
"fractional seconds, 1 digit" etc. or something like that.

And what about "tenths of seconds", "hundredths of seconds"?

Yes, those are much better.

cheers

andrew

--
Andrew Dunstan https://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#14Thomas Munro
thomas.munro@gmail.com
In reply to: Andrew Dunstan (#13)
Re: Support for jsonpath .datetime() method

On Sat, Jul 27, 2019 at 2:43 AM Andrew Dunstan
<andrew.dunstan@2ndquadrant.com> wrote:

On 7/23/19 6:48 PM, Nikita Glukhov wrote:

Some concrete pieces of review:

+       <row>
+        <entry><literal>FF1</literal></entry>
+        <entry>decisecond (0-9)</entry>
+       </row>

Let's not use such weird terms as "deciseconds". We could say
"fractional seconds, 1 digit" etc. or something like that.

And what about "tenths of seconds", "hundredths of seconds"?

Yes, those are much better.

I've moved this to the September CF, still in "Waiting on Author" state.

--
Thomas Munro
https://enterprisedb.com

#15Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Thomas Munro (#14)
Re: Support for jsonpath .datetime() method

On Thu, Aug 1, 2019 at 1:31 PM Thomas Munro <thomas.munro@gmail.com> wrote:

On Sat, Jul 27, 2019 at 2:43 AM Andrew Dunstan
<andrew.dunstan@2ndquadrant.com> wrote:

On 7/23/19 6:48 PM, Nikita Glukhov wrote:

Some concrete pieces of review:

+       <row>
+        <entry><literal>FF1</literal></entry>
+        <entry>decisecond (0-9)</entry>
+       </row>

Let's not use such weird terms as "deciseconds". We could say
"fractional seconds, 1 digit" etc. or something like that.

And what about "tenths of seconds", "hundredths of seconds"?

Yes, those are much better.

I've moved this to the September CF, still in "Waiting on Author" state.

I'd like to summarize differences between standard datetime parsing
and our to_timestamp()/to_date().

1) Standard defines much less datetime template parts. Namely it defines:
YYYY | YYY | YY | Y
RRRR | RR
MM
DD
DDD
HH | HH12
HH24
MI
SS
SSSSS
FF1 | FF2 | FF3 | FF4 | FF5 | FF6 | FF7 | FF8 | FF9
A.M. | P.M.
TZH
TZM

We support majority of them and much more. Incompatibilities are:
* SSSS (our name is SSSSS),
* We don't support RRRR | RR,
* Our handling of YYYY | YYY | YY | Y is different. What we have
here is more like RRRR | RR in standard (Nikita explained that
upthread [1]),
* We don't support FF[1-9]. FF[1-6] are implemented in patch. We
can't support FF[7-9], because our binary representation of timestamp
datatype don't have enough of precision.

2) Standard defines only following delimiters: <minus sign>, <period>,
<solidus>, <comma>, <apostrophe>, <semicolon>, <colon>, <space>. And
it requires strict matching of separators between template and input
strings. We don't do so either in FX or non-FX mode.

For instance, we allow both to_date('2019/12/31', 'YYYY-MM-DD') and
to_date('2019/12/31', 'FXYYYY-MM-DD'). But according to standard this
date should be written only as '2019-12-31' to match given template
string.

3) Standard prescribes recognition of digits according to \p{Nd}
regex. \p{Nd} matches to "a digit zero through nine in any script
except ideographic scripts". As far as I remember, we currently do
recognize only ASCII digits.

4) For non-delimited template parts standard requires matching to
digit sequences of lengths between 1 and maximum number of characters
of that template part. We don't always do so. For instance, we allow
more than 4 digits to correspond to YYYY, more than 3 digits to
correspond to YYY and so on.

# select to_date('2019-12-31', 'YYY-MM-DD');
to_date
------------
2019-12-31
(1 row)

Links.

1. /messages/by-id/d6efab15-f3a4-40d6-8ddb-6fd8f64cbc08@postgrespro.ru

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#16Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Alexander Korotkov (#15)
Re: Support for jsonpath .datetime() method

On Tue, Aug 13, 2019 at 12:08 AM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

On Thu, Aug 1, 2019 at 1:31 PM Thomas Munro <thomas.munro@gmail.com> wrote:

On Sat, Jul 27, 2019 at 2:43 AM Andrew Dunstan
<andrew.dunstan@2ndquadrant.com> wrote:

On 7/23/19 6:48 PM, Nikita Glukhov wrote:

Some concrete pieces of review:

+       <row>
+        <entry><literal>FF1</literal></entry>
+        <entry>decisecond (0-9)</entry>
+       </row>

Let's not use such weird terms as "deciseconds". We could say
"fractional seconds, 1 digit" etc. or something like that.

And what about "tenths of seconds", "hundredths of seconds"?

Yes, those are much better.

I've moved this to the September CF, still in "Waiting on Author" state.

I'd like to summarize differences between standard datetime parsing
and our to_timestamp()/to_date().

Let me describe my proposal to overcome these differences.

1) Standard defines much less datetime template parts. Namely it defines:
YYYY | YYY | YY | Y
RRRR | RR
MM
DD
DDD
HH | HH12
HH24
MI
SS
SSSSS
FF1 | FF2 | FF3 | FF4 | FF5 | FF6 | FF7 | FF8 | FF9
A.M. | P.M.
TZH
TZM

We support majority of them and much more.

Regarding non-contradicting template parts we can support them in
.datetime() method too. That would be our extension to standard. See
no problem here.

Incompatibilities are:
* SSSS (our name is SSSSS),

Since SSSS is not reserved, I'd propose to make SSSS an alias for SSSSS.

* We don't support RRRR | RR,
* Our handling of YYYY | YYY | YY | Y is different. What we have
here is more like RRRR | RR in standard (Nikita explained that
upthread [1]),

I'd like to make YYYY | YYY | YY | Y and RRRR | RR behavior standard
conforming in both to_timestamp()/to_date() and .datetime(). Handling
these template parts differently in different functions would be
confusing for users.

* We don't support FF[1-9]. FF[1-6] are implemented in patch. We
can't support FF[7-9], because our binary representation of timestamp
datatype don't have enough of precision.

I propose to postpone implementation of FF[7-9]. We can support them
later once we have precise enough datatypes.

2) Standard defines only following delimiters: <minus sign>, <period>,
<solidus>, <comma>, <apostrophe>, <semicolon>, <colon>, <space>. And
it requires strict matching of separators between template and input
strings. We don't do so either in FX or non-FX mode.

For instance, we allow both to_date('2019/12/31', 'YYYY-MM-DD') and
to_date('2019/12/31', 'FXYYYY-MM-DD'). But according to standard this
date should be written only as '2019-12-31' to match given template
string.

4) For non-delimited template parts standard requires matching to
digit sequences of lengths between 1 and maximum number of characters
of that template part. We don't always do so. For instance, we allow
more than 4 digits to correspond to YYYY, more than 3 digits to
correspond to YYY and so on.

# select to_date('2019-12-31', 'YYY-MM-DD');
to_date
------------
2019-12-31
(1 row)

In order to implement these I'd like to propose introduction of
special do_to_timestamp() flag, which would define standard conforming
parsing. This flag would be used in .datetime() jsonpath method.
Later we also should use it for CAST(... FORMAT ...) expression, which
should also do standard conforming parsing

3) Standard prescribes recognition of digits according to \p{Nd}
regex. \p{Nd} matches to "a digit zero through nine in any script
except ideographic scripts". As far as I remember, we currently do
recognize only ASCII digits.

Support all unicode digit scripts would be cool for both
to_timestamp()/to_date() and standard parsing. However, I think this
could be postponed. Personally I didn't meet non-ascii digits in
databases yet. If needed one can implement this later, shouldn't be
hard.

If no objections, Nikita and me will work on revised patchset based on
this proposal.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#17Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Alexander Korotkov (#16)
8 attachment(s)
Re: Support for jsonpath .datetime() method

On Mon, Aug 19, 2019 at 1:29 AM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

If no objections, Nikita and me will work on revised patchset based on
this proposal.

Revised patchset is attached. It still requires some polishing. But
the most doubtful part is handling of RR, YYY, YY and Y.

Standard requires us to complete YYY, YY and Y with high digits from
current year. So, if YY matches 99, then year should be 2099, not
1999.

For RR, standard requirements are relaxed. Implementation may choose
matching year from range [current_year - 100; current_year + 100]. It
looks reasonable to handle RR in the same way we currently handle YY:
select appropriate year in [1970; 2069] range. It seems like we
select this range to start in the same point as unix timestamp. But
nowadays it still looks reasonable: it's about +- 50 from current
year. So, years close to the current one are likely completed
correctly. In Oracle RR returns year in [1950; 1949] range. So, it
seems to be designed near 2000 :). I don't think we need to copy this
behavior.

Handling YYY and YY in standard way seems quite easy. We can complete
them as 2YYY and 20YY. This should be standard conforming till 2100.

But handling Y looks problematic. Immutable way of handling this
would work only for decade. Current code completes Y as 200Y and it
looks pretty "outdated" now in 2019. Using current real year would
make conversion timestamp-dependent. This property doesn't look favor
for to_date()/to_timestamp() and unacceptable for immutable jsonpath
functions (but we can forbid using Y pattern there). Current patch
complete Y as 202Y assuming v13 will be released in 2020. But I'm not
sure what is better solution here. The bright side is that I haven't
seen anybody use Y patten in real life :)

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0001-ff1-ff6-datetime-patterns-5.patchapplication/x-patch; name=0001-ff1-ff6-datetime-patterns-5.patchDownload
commit 04e961d6763edfaba778c898e0c59b1392f3a0ea
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Sun Jul 21 01:12:16 2019 +0300

    Support for FF1-FF6 format patterns
    
    Reported-by:
    Bug:
    Discussion:
    Author:
    Reviewed-by:
    Tested-by:
    Backpatch-through:

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index a7abf8c2eee..c41aeeaa3b7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -6122,6 +6122,30 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
         <entry><literal>US</literal></entry>
         <entry>microsecond (000000-999999)</entry>
        </row>
+       <row>
+        <entry><literal>FF1</literal></entry>
+        <entry>decisecond (0-9)</entry>
+       </row>
+       <row>
+        <entry><literal>FF2</literal></entry>
+        <entry>centisecond (00-99)</entry>
+       </row>
+       <row>
+        <entry><literal>FF3</literal></entry>
+        <entry>millisecond (000-999)</entry>
+       </row>
+       <row>
+        <entry><literal>FF4</literal></entry>
+        <entry>tenth of a millisecond (0000-9999)</entry>
+       </row>
+       <row>
+        <entry><literal>FF5</literal></entry>
+        <entry>hundredth of a millisecond (00000-99999)</entry>
+       </row>
+       <row>
+        <entry><literal>FF6</literal></entry>
+        <entry>microsecond (000000-999999)</entry>
+       </row>
        <row>
         <entry><literal>SSSS</literal></entry>
         <entry>seconds past midnight (0-86399)</entry>
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index b3115e4bea8..49edc1c21cd 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -86,6 +86,7 @@
 #endif
 
 #include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
 #include "mb/pg_wchar.h"
 #include "utils/builtins.h"
 #include "utils/date.h"
@@ -434,7 +435,8 @@ typedef struct
 				clock,			/* 12 or 24 hour clock? */
 				tzsign,			/* +1, -1 or 0 if timezone info is absent */
 				tzh,
-				tzm;
+				tzm,
+				ff;				/* fractional precision */
 } TmFromChar;
 
 #define ZERO_tmfc(_X) memset(_X, 0, sizeof(TmFromChar))
@@ -594,6 +596,12 @@ typedef enum
 	DCH_Day,
 	DCH_Dy,
 	DCH_D,
+	DCH_FF1,
+	DCH_FF2,
+	DCH_FF3,
+	DCH_FF4,
+	DCH_FF5,
+	DCH_FF6,
 	DCH_FX,						/* global suffix */
 	DCH_HH24,
 	DCH_HH12,
@@ -643,6 +651,12 @@ typedef enum
 	DCH_dd,
 	DCH_dy,
 	DCH_d,
+	DCH_ff1,
+	DCH_ff2,
+	DCH_ff3,
+	DCH_ff4,
+	DCH_ff5,
+	DCH_ff6,
 	DCH_fx,
 	DCH_hh24,
 	DCH_hh12,
@@ -743,7 +757,13 @@ static const KeyWord DCH_keywords[] = {
 	{"Day", 3, DCH_Day, false, FROM_CHAR_DATE_NONE},
 	{"Dy", 2, DCH_Dy, false, FROM_CHAR_DATE_NONE},
 	{"D", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
-	{"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},	/* F */
+	{"FF1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE},	/* F */
+	{"FF2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE},
+	{"FF3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE},
+	{"FF4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE},
+	{"FF5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE},
+	{"FF6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE},
+	{"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
 	{"HH24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE},	/* H */
 	{"HH12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
 	{"HH", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
@@ -792,7 +812,13 @@ static const KeyWord DCH_keywords[] = {
 	{"dd", 2, DCH_DD, true, FROM_CHAR_DATE_GREGORIAN},
 	{"dy", 2, DCH_dy, false, FROM_CHAR_DATE_NONE},
 	{"d", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
-	{"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},	/* f */
+	{"ff1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE},	/* f */
+	{"ff2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE},
+	{"ff3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE},
+	{"ff4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE},
+	{"ff5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE},
+	{"ff6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE},
+	{"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
 	{"hh24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE},	/* h */
 	{"hh12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
 	{"hh", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
@@ -893,10 +919,10 @@ static const int DCH_index[KeyWord_INDEX_SIZE] = {
 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 	-1, -1, -1, -1, -1, DCH_A_D, DCH_B_C, DCH_CC, DCH_DAY, -1,
-	DCH_FX, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
+	DCH_FF1, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
 	DCH_P_M, DCH_Q, DCH_RM, DCH_SSSS, DCH_TZH, DCH_US, -1, DCH_WW, -1, DCH_Y_YYY,
 	-1, -1, -1, -1, -1, -1, -1, DCH_a_d, DCH_b_c, DCH_cc,
-	DCH_day, -1, DCH_fx, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
+	DCH_day, -1, DCH_ff1, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
 	-1, -1, DCH_p_m, DCH_q, DCH_rm, DCH_ssss, DCH_tz, DCH_us, -1, DCH_ww,
 	-1, DCH_y_yyy, -1, -1, -1, -1
 
@@ -960,7 +986,6 @@ typedef struct NUMProc
 			   *L_currency_symbol;
 } NUMProc;
 
-
 /* ----------
  * Functions
  * ----------
@@ -993,7 +1018,7 @@ static int	from_char_parse_int(int *dest, char **src, FormatNode *node);
 static int	seq_search(char *name, const char *const *array, int type, int max, int *len);
 static int	from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
 static void do_to_timestamp(text *date_txt, text *fmt,
-							struct pg_tm *tm, fsec_t *fsec);
+							struct pg_tm *tm, fsec_t *fsec, int *fprec);
 static char *fill_str(char *str, int c, int max);
 static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
 static char *int_to_roman(int number);
@@ -2515,18 +2540,32 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 					str_numth(s, s, S_TH_TYPE(n->suffix));
 				s += strlen(s);
 				break;
-			case DCH_MS:		/* millisecond */
-				sprintf(s, "%03d", (int) (in->fsec / INT64CONST(1000)));
-				if (S_THth(n->suffix))
-					str_numth(s, s, S_TH_TYPE(n->suffix));
+#define DCH_to_char_fsec(frac_fmt, frac_val) \
+				sprintf(s, frac_fmt, (int) (frac_val)); \
+				if (S_THth(n->suffix)) \
+					str_numth(s, s, S_TH_TYPE(n->suffix)); \
 				s += strlen(s);
+			case DCH_FF1:		/* decisecond */
+				DCH_to_char_fsec("%01d", in->fsec / 100000);
+				break;
+			case DCH_FF2:		/* centisecond */
+				DCH_to_char_fsec("%02d", in->fsec / 10000);
+				break;
+			case DCH_FF3:
+			case DCH_MS:		/* millisecond */
+				DCH_to_char_fsec("%03d", in->fsec / 1000);
 				break;
+			case DCH_FF4:
+				DCH_to_char_fsec("%04d", in->fsec / 100);
+				break;
+			case DCH_FF5:
+				DCH_to_char_fsec("%05d", in->fsec / 10);
+				break;
+			case DCH_FF6:
 			case DCH_US:		/* microsecond */
-				sprintf(s, "%06d", (int) in->fsec);
-				if (S_THth(n->suffix))
-					str_numth(s, s, S_TH_TYPE(n->suffix));
-				s += strlen(s);
+				DCH_to_char_fsec("%06d", in->fsec);
 				break;
+#undef DCH_to_char_fsec
 			case DCH_SSSS:
 				sprintf(s, "%d", tm->tm_hour * SECS_PER_HOUR +
 						tm->tm_min * SECS_PER_MINUTE +
@@ -3151,8 +3190,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 
 				SKIP_THth(s, n->suffix);
 				break;
+			case DCH_FF1:
+			case DCH_FF2:
+			case DCH_FF3:
+			case DCH_FF4:
+			case DCH_FF5:
+			case DCH_FF6:
+				out->ff = n->key->id - DCH_FF1 + 1;
+				/* fall through */
 			case DCH_US:		/* microsecond */
-				len = from_char_parse_int_len(&out->us, &s, 6, n);
+				len = from_char_parse_int_len(&out->us, &s,
+											  n->key->id == DCH_US ? 6 :
+											  out->ff, n);
 
 				out->us *= len == 1 ? 100000 :
 					len == 2 ? 10000 :
@@ -3686,8 +3735,9 @@ to_timestamp(PG_FUNCTION_ARGS)
 	int			tz;
 	struct pg_tm tm;
 	fsec_t		fsec;
+	int			fprec;
 
-	do_to_timestamp(date_txt, fmt, &tm, &fsec);
+	do_to_timestamp(date_txt, fmt, &tm, &fsec, &fprec);
 
 	/* Use the specified time zone, if any. */
 	if (tm.tm_zone)
@@ -3705,6 +3755,10 @@ to_timestamp(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 				 errmsg("timestamp out of range")));
 
+	/* Use the specified fractional precision, if any. */
+	if (fprec)
+		AdjustTimestampForTypmod(&result, fprec);
+
 	PG_RETURN_TIMESTAMP(result);
 }
 
@@ -3722,7 +3776,7 @@ to_date(PG_FUNCTION_ARGS)
 	struct pg_tm tm;
 	fsec_t		fsec;
 
-	do_to_timestamp(date_txt, fmt, &tm, &fsec);
+	do_to_timestamp(date_txt, fmt, &tm, &fsec, NULL);
 
 	/* Prevent overflow in Julian-day routines */
 	if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3746,8 +3800,8 @@ to_date(PG_FUNCTION_ARGS)
 /*
  * do_to_timestamp: shared code for to_timestamp and to_date
  *
- * Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm
- * and fractional seconds.
+ * Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm,
+ * fractional seconds, and fractional precision.
  *
  * We parse 'fmt' into a list of FormatNodes, which is then passed to
  * DCH_from_char to populate a TmFromChar with the parsed contents of
@@ -3758,7 +3812,7 @@ to_date(PG_FUNCTION_ARGS)
  */
 static void
 do_to_timestamp(text *date_txt, text *fmt,
-				struct pg_tm *tm, fsec_t *fsec)
+				struct pg_tm *tm, fsec_t *fsec, int *fprec)
 {
 	FormatNode *format;
 	TmFromChar	tmfc;
@@ -3814,6 +3868,7 @@ do_to_timestamp(text *date_txt, text *fmt,
 		DCH_from_char(format, date_str, &tmfc);
 
 		pfree(fmt_str);
+
 		if (!incache)
 			pfree(format);
 	}
@@ -3995,6 +4050,8 @@ do_to_timestamp(text *date_txt, text *fmt,
 		*fsec += tmfc.ms * 1000;
 	if (tmfc.us)
 		*fsec += tmfc.us;
+	if (fprec)
+		*fprec = tmfc.ff;		/* fractional precision, if specified */
 
 	/* Range-check date fields according to bit mask computed above */
 	if (fmask != 0)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 5861ffbbc97..2931bd58c0d 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -70,7 +70,6 @@ typedef struct
 
 static TimeOffset time2t(const int hour, const int min, const int sec, const fsec_t fsec);
 static Timestamp dt2local(Timestamp dt, int timezone);
-static void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
 static void AdjustIntervalForTypmod(Interval *interval, int32 typmod);
 static TimestampTz timestamp2timestamptz(Timestamp timestamp);
 static Timestamp timestamptz2timestamp(TimestampTz timestamp);
@@ -333,7 +332,7 @@ timestamp_scale(PG_FUNCTION_ARGS)
  * AdjustTimestampForTypmod --- round off a timestamp to suit given typmod
  * Works for either timestamp or timestamptz.
  */
-static void
+void
 AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
 {
 	static const int64 TimestampScales[MAX_TIMESTAMP_PRECISION + 1] = {
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index b8a199cdded..0cafdd26538 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -336,4 +336,6 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
 												   int n);
 extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
 
+extern void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
+
 #endif							/* DATETIME_H */
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index b2b45773339..74ecb7c10e6 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -2786,6 +2786,85 @@ SELECT to_timestamp('2011-12-18 11:38 20',     'YYYY-MM-DD HH12:MI TZM');
  Sun Dec 18 03:18:00 2011 PST
 (1 row)
 
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |         to_timestamp         
+---+------------------------------
+ 1 | Fri Nov 02 12:34:56 2018 PDT
+ 2 | Fri Nov 02 12:34:56 2018 PDT
+ 3 | Fri Nov 02 12:34:56 2018 PDT
+ 4 | Fri Nov 02 12:34:56 2018 PDT
+ 5 | Fri Nov 02 12:34:56 2018 PDT
+ 6 | Fri Nov 02 12:34:56 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |          to_timestamp          
+---+--------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.1 2018 PDT
+ 3 | Fri Nov 02 12:34:56.1 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |          to_timestamp           
+---+---------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.12 2018 PDT
+ 4 | Fri Nov 02 12:34:56.12 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |           to_timestamp           
+---+----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.123 2018 PDT
+ 5 | Fri Nov 02 12:34:56.123 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |           to_timestamp            
+---+-----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1234 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |            to_timestamp            
+---+------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12345 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12345 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |            to_timestamp             
+---+-------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12346 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123456 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ERROR:  date/time field value out of range: "2018-11-02 12:34:56.123456789"
 --
 -- Check handling of multiple spaces in format and/or input
 --
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index 715680e3302..f772b07d5a4 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -1584,6 +1584,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
             | 2001 1 1 1 1 1 1
 (65 rows)
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamp),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+ to_char_12 |                              to_char                               
+------------+--------------------------------------------------------------------
+            | 0 00 000 0000 00000 000000  0 00 000 0000 00000 000000  000 000000
+            | 7 78 780 7800 78000 780000  7 78 780 7800 78000 780000  780 780000
+            | 7 78 789 7890 78901 789010  7 78 789 7890 78901 789010  789 789010
+            | 7 78 789 7890 78901 789012  7 78 789 7890 78901 789012  789 789012
+(4 rows)
+
 -- timestamp numeric fields constructor
 SELECT make_timestamp(2014,12,28,6,30,45.887);
         make_timestamp        
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 5551fa6610e..2d6a71ca64b 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -1704,6 +1704,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
             | 2001 1 1 1 1 1 1
 (66 rows)
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamptz),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+ to_char_12 |                              to_char                               
+------------+--------------------------------------------------------------------
+            | 0 00 000 0000 00000 000000  0 00 000 0000 00000 000000  000 000000
+            | 7 78 780 7800 78000 780000  7 78 780 7800 78000 780000  780 780000
+            | 7 78 789 7890 78901 789010  7 78 789 7890 78901 789010  789 789010
+            | 7 78 789 7890 78901 789012  7 78 789 7890 78901 789012  789 789012
+(4 rows)
+
 -- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
 SET timezone = '00:00';
 SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index e356dd563ee..3c8580397ac 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -402,6 +402,15 @@ SELECT to_timestamp('2011-12-18 11:38 +05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
 SELECT to_timestamp('2011-12-18 11:38 -05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
 SELECT to_timestamp('2011-12-18 11:38 20',     'YYYY-MM-DD HH12:MI TZM');
 
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+
 --
 -- Check handling of multiple spaces in format and/or input
 --
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index 031b22bc3c1..329987f7eaa 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -224,5 +224,13 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
 SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
    FROM TIMESTAMP_TBL;
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamp),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+
 -- timestamp numeric fields constructor
 SELECT make_timestamp(2014,12,28,6,30,45.887);
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index 28c76d6b72c..f5fee639a01 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -248,6 +248,14 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
 SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
    FROM TIMESTAMPTZ_TBL;
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamptz),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+
 -- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
 SET timezone = '00:00';
 SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
0003-rr-revise-yy-datetime-patterns-5.patchapplication/x-patch; name=0003-rr-revise-yy-datetime-patterns-5.patchDownload
commit 4811b710256b1d42ca5b967b774669959267f06c
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Tue Aug 20 07:57:11 2019 +0300

    Introduce RR and RRRR, revise YYY, YY and Y format patterns
    
    Reported-by:
    Bug:
    Discussion:
    Author:
    Reviewed-by:
    Tested-by:
    Backpatch-through:

diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 481ef93941e..9ebcdad3cf9 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -626,6 +626,8 @@ typedef enum
 	DCH_PM,
 	DCH_Q,
 	DCH_RM,
+	DCH_RRRR,
+	DCH_RR,
 	DCH_SSSSS,
 	DCH_SSSS,
 	DCH_SS,
@@ -679,6 +681,8 @@ typedef enum
 	DCH_pm,
 	DCH_q,
 	DCH_rm,
+	DCH_rrrr,
+	DCH_rr,
 	DCH_sssss,
 	DCH_ssss,
 	DCH_ss,
@@ -789,6 +793,8 @@ static const KeyWord DCH_keywords[] = {
 	{"PM", 2, DCH_PM, false, FROM_CHAR_DATE_NONE},
 	{"Q", 1, DCH_Q, true, FROM_CHAR_DATE_NONE}, /* Q */
 	{"RM", 2, DCH_RM, false, FROM_CHAR_DATE_GREGORIAN}, /* R */
+	{"RRRR", 4, DCH_RRRR, true, FROM_CHAR_DATE_GREGORIAN},
+	{"RR", 2, DCH_RR, true, FROM_CHAR_DATE_GREGORIAN},
 	{"SSSSS", 5, DCH_SSSS, true, FROM_CHAR_DATE_NONE},	/* S */
 	{"SSSS", 4, DCH_SSSS, true, FROM_CHAR_DATE_NONE},
 	{"SS", 2, DCH_SS, true, FROM_CHAR_DATE_NONE},
@@ -842,6 +848,8 @@ static const KeyWord DCH_keywords[] = {
 	{"pm", 2, DCH_pm, false, FROM_CHAR_DATE_NONE},
 	{"q", 1, DCH_Q, true, FROM_CHAR_DATE_NONE}, /* q */
 	{"rm", 2, DCH_rm, false, FROM_CHAR_DATE_GREGORIAN}, /* r */
+	{"rrrr", 4, DCH_RRRR, true, FROM_CHAR_DATE_GREGORIAN},
+	{"rr", 2, DCH_RR, true, FROM_CHAR_DATE_GREGORIAN},
 	{"sssss", 5, DCH_SSSS, true, FROM_CHAR_DATE_NONE},	/* s */
 	{"ssss", 4, DCH_SSSS, true, FROM_CHAR_DATE_NONE},
 	{"ss", 2, DCH_SS, true, FROM_CHAR_DATE_NONE},
@@ -1013,7 +1021,8 @@ static void dump_node(FormatNode *node, int max);
 
 static const char *get_th(char *num, int type);
 static char *str_numth(char *dest, char *num, int type);
-static int	adjust_partial_year_to_2020(int year);
+static int	adjust_partial_year_to_2020(int year, int ndigits);
+static int	adjust_relative_partial_year_to_2020(int year);
 static int	strspace_len(char *str);
 static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode);
 static void from_char_set_int(int *dest, const int value, const FormatNode *node);
@@ -2157,7 +2166,19 @@ is_next_separator(FormatNode *n)
 
 
 static int
-adjust_partial_year_to_2020(int year)
+adjust_partial_year_to_2020(int year, int ndigits)
+{
+	if (ndigits == 1)
+		return year + 2020;
+	else if (ndigits == 2 || ndigits == 3)
+		return year + 2000;
+	else
+		return year;
+}
+
+
+static int
+adjust_relative_partial_year_to_2020(int year)
 {
 	/*
 	 * Adjust all dates toward 2020; this is effectively what happens when we
@@ -2958,6 +2979,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 					str_numth(s, s, S_TH_TYPE(n->suffix));
 				s += strlen(s);
 				break;
+			case DCH_RRRR:
 			case DCH_YYYY:
 			case DCH_IYYY:
 				sprintf(s, "%0*d",
@@ -2988,6 +3010,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 					str_numth(s, s, S_TH_TYPE(n->suffix));
 				s += strlen(s);
 				break;
+			case DCH_RR:
 			case DCH_YY:
 			case DCH_IY:
 				sprintf(s, "%0*d",
@@ -3373,6 +3396,7 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 					SKIP_THth(s, n->suffix);
 				}
 				break;
+			case DCH_RRRR:
 			case DCH_YYYY:
 			case DCH_IYYY:
 				from_char_parse_int(&out->year, &s, n);
@@ -3381,22 +3405,28 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 				break;
 			case DCH_YYY:
 			case DCH_IYY:
-				if (from_char_parse_int(&out->year, &s, n) < 4)
-					out->year = adjust_partial_year_to_2020(out->year);
+				len = from_char_parse_int(&out->year, &s, n);
+				out->year = adjust_partial_year_to_2020(out->year, len);
 				out->yysz = 3;
 				SKIP_THth(s, n->suffix);
 				break;
+			case DCH_RR:
+				from_char_parse_int(&out->year, &s, n);
+				out->year = adjust_relative_partial_year_to_2020(out->year);
+				out->yysz = 2;
+				SKIP_THth(s, n->suffix);
+				break;
 			case DCH_YY:
 			case DCH_IY:
-				if (from_char_parse_int(&out->year, &s, n) < 4)
-					out->year = adjust_partial_year_to_2020(out->year);
+				len = from_char_parse_int(&out->year, &s, n);
+				out->year = adjust_partial_year_to_2020(out->year, len);
 				out->yysz = 2;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Y:
 			case DCH_I:
-				if (from_char_parse_int(&out->year, &s, n) < 4)
-					out->year = adjust_partial_year_to_2020(out->year);
+				len = from_char_parse_int(&out->year, &s, n);
+				out->year = adjust_partial_year_to_2020(out->year, len);
 				out->yysz = 1;
 				SKIP_THth(s, n->suffix);
 				break;
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index 6b53876e062..f68882cc173 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -2597,7 +2597,7 @@ SELECT to_timestamp('1,582nd VIII 21', 'Y,YYYth FMRM DD');
 (1 row)
 
 SELECT to_timestamp('15 "text between quote marks" 98 54 45',
-                    E'HH24 "\\"text between quote marks\\"" YY MI SS');
+                    E'HH24 "\\"text between quote marks\\"" RR MI SS');
          to_timestamp         
 ------------------------------
  Thu Jan 01 15:54:45 1998 PST
@@ -2618,19 +2618,22 @@ SELECT to_timestamp('2000January09Sunday', 'YYYYFMMonthDDFMDay');
 SELECT to_timestamp('97/Feb/16', 'YYMonDD');
 ERROR:  invalid value "/Fe" for "Mon"
 DETAIL:  The given value did not match any of the allowed values for this field.
+SELECT to_timestamp('97/Feb/16', 'RRMonDD');
+ERROR:  invalid value "/Fe" for "Mon"
+DETAIL:  The given value did not match any of the allowed values for this field.
 SELECT to_timestamp('97/Feb/16', 'YY:Mon:DD');
          to_timestamp         
 ------------------------------
- Sun Feb 16 00:00:00 1997 PST
+ Sat Feb 16 00:00:00 2097 PST
 (1 row)
 
-SELECT to_timestamp('97/Feb/16', 'FXYY:Mon:DD');
+SELECT to_timestamp('97/Feb/16', 'FXRR:Mon:DD');
          to_timestamp         
 ------------------------------
  Sun Feb 16 00:00:00 1997 PST
 (1 row)
 
-SELECT to_timestamp('97/Feb/16', 'FXYY/Mon/DD');
+SELECT to_timestamp('97/Feb/16', 'FXRR/Mon/DD');
          to_timestamp         
 ------------------------------
  Sun Feb 16 00:00:00 1997 PST
@@ -2642,6 +2645,12 @@ SELECT to_timestamp('19971116', 'YYYYMMDD');
  Sun Nov 16 00:00:00 1997 PST
 (1 row)
 
+SELECT to_timestamp('19971116', 'RRRRMMDD');
+         to_timestamp         
+------------------------------
+ Sun Nov 16 00:00:00 1997 PST
+(1 row)
+
 SELECT to_timestamp('20000-1116', 'YYYY-MMDD');
          to_timestamp          
 -------------------------------
@@ -2663,11 +2672,17 @@ SELECT to_timestamp('1997 BC 11 16', 'YYYY BC MM DD');
 SELECT to_timestamp('9-1116', 'Y-MMDD');
          to_timestamp         
 ------------------------------
- Mon Nov 16 00:00:00 2009 PST
+ Fri Nov 16 00:00:00 2029 PST
 (1 row)
 
 SELECT to_timestamp('95-1116', 'YY-MMDD');
          to_timestamp         
+------------------------------
+ Wed Nov 16 00:00:00 2095 PST
+(1 row)
+
+SELECT to_timestamp('95-1116', 'RR-MMDD');
+         to_timestamp         
 ------------------------------
  Thu Nov 16 00:00:00 1995 PST
 (1 row)
@@ -2675,7 +2690,7 @@ SELECT to_timestamp('95-1116', 'YY-MMDD');
 SELECT to_timestamp('995-1116', 'YYY-MMDD');
          to_timestamp         
 ------------------------------
- Thu Nov 16 00:00:00 1995 PST
+ Mon Nov 16 00:00:00 2995 PST
 (1 row)
 
 SELECT to_timestamp('2005426', 'YYYYWWD');
@@ -2711,7 +2726,7 @@ SELECT to_timestamp('05527', 'IYIWID');
 SELECT to_timestamp('5527', 'IIWID');
          to_timestamp         
 ------------------------------
- Sun Jan 01 00:00:00 2006 PST
+ Sun Dec 28 00:00:00 2025 PST
 (1 row)
 
 SELECT to_timestamp('2005364', 'IYYYIDDD');
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7a9da1e954..e35cb448508 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -344,7 +344,7 @@ SELECT to_timestamp('My birthday-> Year: 1976, Month: May, Day: 16',
 SELECT to_timestamp('1,582nd VIII 21', 'Y,YYYth FMRM DD');
 
 SELECT to_timestamp('15 "text between quote marks" 98 54 45',
-                    E'HH24 "\\"text between quote marks\\"" YY MI SS');
+                    E'HH24 "\\"text between quote marks\\"" RR MI SS');
 
 SELECT to_timestamp('05121445482000', 'MMDDHH24MISSYYYY');
 
@@ -352,14 +352,18 @@ SELECT to_timestamp('2000January09Sunday', 'YYYYFMMonthDDFMDay');
 
 SELECT to_timestamp('97/Feb/16', 'YYMonDD');
 
+SELECT to_timestamp('97/Feb/16', 'RRMonDD');
+
 SELECT to_timestamp('97/Feb/16', 'YY:Mon:DD');
 
-SELECT to_timestamp('97/Feb/16', 'FXYY:Mon:DD');
+SELECT to_timestamp('97/Feb/16', 'FXRR:Mon:DD');
 
-SELECT to_timestamp('97/Feb/16', 'FXYY/Mon/DD');
+SELECT to_timestamp('97/Feb/16', 'FXRR/Mon/DD');
 
 SELECT to_timestamp('19971116', 'YYYYMMDD');
 
+SELECT to_timestamp('19971116', 'RRRRMMDD');
+
 SELECT to_timestamp('20000-1116', 'YYYY-MMDD');
 
 SELECT to_timestamp('1997 AD 11 16', 'YYYY BC MM DD');
@@ -369,6 +373,8 @@ SELECT to_timestamp('9-1116', 'Y-MMDD');
 
 SELECT to_timestamp('95-1116', 'YY-MMDD');
 
+SELECT to_timestamp('95-1116', 'RR-MMDD');
+
 SELECT to_timestamp('995-1116', 'YYY-MMDD');
 
 SELECT to_timestamp('2005426', 'YYYYWWD');
0002-sssss-datetime-pattern-5.patchapplication/x-patch; name=0002-sssss-datetime-pattern-5.patchDownload
commit 2b75269d4c1e7a829cf1eac4f6a2a0ea000ed916
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Tue Aug 20 08:38:20 2019 +0300

    Support for SSSSS format pattern
    
    Reported-by:
    Bug:
    Discussion:
    Author:
    Reviewed-by:
    Tested-by:
    Backpatch-through:

diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 49edc1c21cd..481ef93941e 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -626,6 +626,7 @@ typedef enum
 	DCH_PM,
 	DCH_Q,
 	DCH_RM,
+	DCH_SSSSS,
 	DCH_SSSS,
 	DCH_SS,
 	DCH_TZH,
@@ -678,6 +679,7 @@ typedef enum
 	DCH_pm,
 	DCH_q,
 	DCH_rm,
+	DCH_sssss,
 	DCH_ssss,
 	DCH_ss,
 	DCH_tz,
@@ -787,7 +789,8 @@ static const KeyWord DCH_keywords[] = {
 	{"PM", 2, DCH_PM, false, FROM_CHAR_DATE_NONE},
 	{"Q", 1, DCH_Q, true, FROM_CHAR_DATE_NONE}, /* Q */
 	{"RM", 2, DCH_RM, false, FROM_CHAR_DATE_GREGORIAN}, /* R */
-	{"SSSS", 4, DCH_SSSS, true, FROM_CHAR_DATE_NONE},	/* S */
+	{"SSSSS", 5, DCH_SSSS, true, FROM_CHAR_DATE_NONE},	/* S */
+	{"SSSS", 4, DCH_SSSS, true, FROM_CHAR_DATE_NONE},
 	{"SS", 2, DCH_SS, true, FROM_CHAR_DATE_NONE},
 	{"TZH", 3, DCH_TZH, false, FROM_CHAR_DATE_NONE},	/* T */
 	{"TZM", 3, DCH_TZM, true, FROM_CHAR_DATE_NONE},
@@ -839,7 +842,8 @@ static const KeyWord DCH_keywords[] = {
 	{"pm", 2, DCH_pm, false, FROM_CHAR_DATE_NONE},
 	{"q", 1, DCH_Q, true, FROM_CHAR_DATE_NONE}, /* q */
 	{"rm", 2, DCH_rm, false, FROM_CHAR_DATE_GREGORIAN}, /* r */
-	{"ssss", 4, DCH_SSSS, true, FROM_CHAR_DATE_NONE},	/* s */
+	{"sssss", 5, DCH_SSSS, true, FROM_CHAR_DATE_NONE},	/* s */
+	{"ssss", 4, DCH_SSSS, true, FROM_CHAR_DATE_NONE},
 	{"ss", 2, DCH_SS, true, FROM_CHAR_DATE_NONE},
 	{"tz", 2, DCH_tz, false, FROM_CHAR_DATE_NONE},	/* t */
 	{"us", 2, DCH_US, true, FROM_CHAR_DATE_NONE},	/* u */
@@ -920,10 +924,10 @@ static const int DCH_index[KeyWord_INDEX_SIZE] = {
 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 	-1, -1, -1, -1, -1, DCH_A_D, DCH_B_C, DCH_CC, DCH_DAY, -1,
 	DCH_FF1, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
-	DCH_P_M, DCH_Q, DCH_RM, DCH_SSSS, DCH_TZH, DCH_US, -1, DCH_WW, -1, DCH_Y_YYY,
+	DCH_P_M, DCH_Q, DCH_RM, DCH_SSSSS, DCH_TZH, DCH_US, -1, DCH_WW, -1, DCH_Y_YYY,
 	-1, -1, -1, -1, -1, -1, -1, DCH_a_d, DCH_b_c, DCH_cc,
 	DCH_day, -1, DCH_ff1, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
-	-1, -1, DCH_p_m, DCH_q, DCH_rm, DCH_ssss, DCH_tz, DCH_us, -1, DCH_ww,
+	-1, -1, DCH_p_m, DCH_q, DCH_rm, DCH_sssss, DCH_tz, DCH_us, -1, DCH_ww,
 	-1, DCH_y_yyy, -1, -1, -1, -1
 
 	/*---- chars over 126 are skipped ----*/
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index 74ecb7c10e6..6b53876e062 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -3077,6 +3077,14 @@ SELECT to_timestamp('2015-02-11 86000', 'YYYY-MM-DD SSSS');  -- ok
 
 SELECT to_timestamp('2015-02-11 86400', 'YYYY-MM-DD SSSS');
 ERROR:  date/time field value out of range: "2015-02-11 86400"
+SELECT to_timestamp('2015-02-11 86000', 'YYYY-MM-DD SSSSS');  -- ok
+         to_timestamp         
+------------------------------
+ Wed Feb 11 23:53:20 2015 PST
+(1 row)
+
+SELECT to_timestamp('2015-02-11 86400', 'YYYY-MM-DD SSSSS');
+ERROR:  date/time field value out of range: "2015-02-11 86400"
 SELECT to_date('2016-13-10', 'YYYY-MM-DD');
 ERROR:  date/time field value out of range: "2016-13-10"
 SELECT to_date('2016-02-30', 'YYYY-MM-DD');
@@ -3140,4 +3148,16 @@ SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD HH:MI:SS TZ');
  2012-12-12 12:00:00 -01:30
 (1 row)
 
+SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD SSSS');
+     to_char      
+------------------
+ 2012-12-12 43200
+(1 row)
+
+SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD SSSSS');
+     to_char      
+------------------
+ 2012-12-12 43200
+(1 row)
+
 RESET TIME ZONE;
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index 3c8580397ac..f7a9da1e954 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -480,6 +480,8 @@ SELECT to_timestamp('2016-02-29 15:50:55', 'YYYY-MM-DD HH24:MI:SS');  -- ok
 SELECT to_timestamp('2015-02-29 15:50:55', 'YYYY-MM-DD HH24:MI:SS');
 SELECT to_timestamp('2015-02-11 86000', 'YYYY-MM-DD SSSS');  -- ok
 SELECT to_timestamp('2015-02-11 86400', 'YYYY-MM-DD SSSS');
+SELECT to_timestamp('2015-02-11 86000', 'YYYY-MM-DD SSSSS');  -- ok
+SELECT to_timestamp('2015-02-11 86400', 'YYYY-MM-DD SSSSS');
 SELECT to_date('2016-13-10', 'YYYY-MM-DD');
 SELECT to_date('2016-02-30', 'YYYY-MM-DD');
 SELECT to_date('2016-02-29', 'YYYY-MM-DD');  -- ok
@@ -503,5 +505,7 @@ SELECT '2012-12-12 12:00'::timestamptz;
 SELECT '2012-12-12 12:00 America/New_York'::timestamptz;
 
 SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD HH:MI:SS TZ');
+SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD SSSS');
+SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD SSSSS');
 
 RESET TIME ZONE;
0005-parse_datetime-function-5.patchapplication/x-patch; name=0005-parse_datetime-function-5.patchDownload
commit b0891b0055b4581ccaa6c71345b53436f955983b
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Mon Aug 26 05:34:19 2019 +0300

    parse_datetime() function
    
    Reported-by:
    Bug:
    Discussion:
    Author:
    Reviewed-by:
    Tested-by:
    Backpatch-through:

diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 4b1afb10f92..9e291b5c7bc 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -41,11 +41,6 @@
 #endif
 
 
-static int	tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
-static int	tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
-static void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
-
-
 /* common code for timetypmodin and timetztypmodin */
 static int32
 anytime_typmodin(bool istz, ArrayType *ta)
@@ -1203,7 +1198,7 @@ time_in(PG_FUNCTION_ARGS)
 /* tm2time()
  * Convert a tm structure to a time data type.
  */
-static int
+int
 tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result)
 {
 	*result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec)
@@ -1379,7 +1374,7 @@ time_scale(PG_FUNCTION_ARGS)
  * have a fundamental tie together but rather a coincidence of
  * implementation. - thomas
  */
-static void
+void
 AdjustTimeForTypmod(TimeADT *time, int32 typmod)
 {
 	static const int64 TimeScales[MAX_TIME_PRECISION + 1] = {
@@ -1957,7 +1952,7 @@ time_part(PG_FUNCTION_ARGS)
 /* tm2timetz()
  * Convert a tm structure to a time data type.
  */
-static int
+int
 tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result)
 {
 	result->time = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) *
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 142d9d2f678..deadd7bd14e 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -1000,6 +1000,11 @@ typedef struct NUMProc
 			   *L_currency_symbol;
 } NUMProc;
 
+/* Return flags for DCH_from_char() */
+#define DCH_DATED	0x01
+#define DCH_TIMED	0x02
+#define DCH_ZONED	0x04
+
 /* ----------
  * Functions
  * ----------
@@ -1034,7 +1039,8 @@ static int	from_char_parse_int(int *dest, char **src, FormatNode *node);
 static int	seq_search(char *name, const char *const *array, int type, int max, int *len);
 static int	from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
 static void do_to_timestamp(text *date_txt, text *fmt, bool std,
-							struct pg_tm *tm, fsec_t *fsec, int *fprec);
+							struct pg_tm *tm, fsec_t *fsec, int *fprec,
+							uint32 *flags);
 static char *fill_str(char *str, int c, int max);
 static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
 static char *int_to_roman(int number);
@@ -3545,6 +3551,111 @@ DCH_prevent_counter_overflow(void)
 	}
 }
 
+/* Get mask of date/time/zone components present in format nodes. */
+static int
+DCH_datetime_type(FormatNode *node)
+{
+	FormatNode *n;
+	int			flags = 0;
+
+	for (n = node; n->type != NODE_TYPE_END; n++)
+	{
+		if (n->type != NODE_TYPE_ACTION)
+			continue;
+
+		switch (n->key->id)
+		{
+			case DCH_FX:
+				break;
+			case DCH_A_M:
+			case DCH_P_M:
+			case DCH_a_m:
+			case DCH_p_m:
+			case DCH_AM:
+			case DCH_PM:
+			case DCH_am:
+			case DCH_pm:
+			case DCH_HH:
+			case DCH_HH12:
+			case DCH_HH24:
+			case DCH_MI:
+			case DCH_SS:
+			case DCH_MS:		/* millisecond */
+			case DCH_US:		/* microsecond */
+			case DCH_FF1:
+			case DCH_FF2:
+			case DCH_FF3:
+			case DCH_FF4:
+			case DCH_FF5:
+			case DCH_FF6:
+			case DCH_SSSS:
+				flags |= DCH_TIMED;
+				break;
+			case DCH_tz:
+			case DCH_TZ:
+			case DCH_OF:
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("formatting field \"%s\" is only supported in to_char",
+								n->key->name)));
+				flags |= DCH_ZONED;
+				break;
+			case DCH_TZH:
+			case DCH_TZM:
+				flags |= DCH_ZONED;
+				break;
+			case DCH_A_D:
+			case DCH_B_C:
+			case DCH_a_d:
+			case DCH_b_c:
+			case DCH_AD:
+			case DCH_BC:
+			case DCH_ad:
+			case DCH_bc:
+			case DCH_MONTH:
+			case DCH_Month:
+			case DCH_month:
+			case DCH_MON:
+			case DCH_Mon:
+			case DCH_mon:
+			case DCH_MM:
+			case DCH_DAY:
+			case DCH_Day:
+			case DCH_day:
+			case DCH_DY:
+			case DCH_Dy:
+			case DCH_dy:
+			case DCH_DDD:
+			case DCH_IDDD:
+			case DCH_DD:
+			case DCH_D:
+			case DCH_ID:
+			case DCH_WW:
+			case DCH_Q:
+			case DCH_CC:
+			case DCH_Y_YYY:
+			case DCH_YYYY:
+			case DCH_IYYY:
+			case DCH_RRRR:
+			case DCH_YYY:
+			case DCH_IYY:
+			case DCH_YY:
+			case DCH_IY:
+			case DCH_RR:
+			case DCH_Y:
+			case DCH_I:
+			case DCH_RM:
+			case DCH_rm:
+			case DCH_W:
+			case DCH_J:
+				flags |= DCH_DATED;
+				break;
+		}
+	}
+
+	return flags;
+}
+
 /* select a DCHCacheEntry to hold the given format picture */
 static DCHCacheEntry *
 DCH_cache_getnew(const char *str, bool std)
@@ -3836,7 +3947,7 @@ to_timestamp(PG_FUNCTION_ARGS)
 	fsec_t		fsec;
 	int			fprec;
 
-	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL);
 
 	/* Use the specified time zone, if any. */
 	if (tm.tm_zone)
@@ -3875,7 +3986,7 @@ to_date(PG_FUNCTION_ARGS)
 	struct pg_tm tm;
 	fsec_t		fsec;
 
-	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL);
 
 	/* Prevent overflow in Julian-day routines */
 	if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3896,6 +4007,164 @@ to_date(PG_FUNCTION_ARGS)
 	PG_RETURN_DATEADT(result);
 }
 
+/*
+ * Convert the 'date_txt' input to a datetime type using argument 'fmt' as a format string.
+ * The actual data type (returned in 'typid', 'typmod') is determined by
+ * the presence of date/time/zone components in the format string.
+ *
+ * When timezone component is present, the corresponding offset is set to '*tz'.
+ */
+Datum
+parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
+			   int32 *typmod, int *tz)
+{
+	struct pg_tm tm;
+	fsec_t		fsec;
+	int			fprec = 0;
+	uint32		flags;
+
+	do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags);
+
+	*typmod = fprec ? fprec : -1;	/* fractional part precision */
+
+	if (flags & DCH_DATED)
+	{
+		if (flags & DCH_TIMED)
+		{
+			if (flags & DCH_ZONED)
+			{
+				TimestampTz result;
+
+				if (tm.tm_zone)
+				{
+					int			dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
+
+					if (dterr)
+						DateTimeParseError(dterr, text_to_cstring(date_txt), "timestamptz");
+				}
+				else
+				{
+					if (*tz == PG_INT32_MIN)
+						ereport(ERROR,
+								(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								 errmsg("missing time zone in input string for type timestamptz")));
+				}
+
+				if (tm2timestamp(&tm, fsec, tz, &result) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("timestamptz out of range")));
+
+				AdjustTimestampForTypmod(&result, *typmod);
+
+				*typid = TIMESTAMPTZOID;
+				return TimestampTzGetDatum(result);
+			}
+			else
+			{
+				Timestamp	result;
+
+				if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("timestamp out of range")));
+
+				AdjustTimestampForTypmod(&result, *typmod);
+
+				*typid = TIMESTAMPOID;
+				return TimestampGetDatum(result);
+			}
+		}
+		else
+		{
+			if (flags & DCH_ZONED)
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+						 errmsg("datetime format is zoned but not timed")));
+			}
+			else
+			{
+				DateADT		result;
+
+				/* Prevent overflow in Julian-day routines */
+				if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("date out of range: \"%s\"",
+									text_to_cstring(date_txt))));
+
+				result = 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(result))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("date out of range: \"%s\"",
+									text_to_cstring(date_txt))));
+
+				*typid = DATEOID;
+				return DateADTGetDatum(result);
+			}
+		}
+	}
+	else if (flags & DCH_TIMED)
+	{
+		if (flags & DCH_ZONED)
+		{
+			TimeTzADT  *result = palloc(sizeof(TimeTzADT));
+
+			if (tm.tm_zone)
+			{
+				int			dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
+
+				if (dterr)
+					DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz");
+			}
+			else
+			{
+				if (*tz == PG_INT32_MIN)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							 errmsg("missing time zone in input string for type timetz")));
+			}
+
+			if (tm2timetz(&tm, fsec, *tz, result) != 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("timetz out of range")));
+
+			AdjustTimeForTypmod(&result->time, *typmod);
+
+			*typid = TIMETZOID;
+			return TimeTzADTPGetDatum(result);
+		}
+		else
+		{
+			TimeADT		result;
+
+			if (tm2time(&tm, fsec, &result) != 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("time out of range")));
+
+			AdjustTimeForTypmod(&result, *typmod);
+
+			*typid = TIMEOID;
+			return TimeADTGetDatum(result);
+		}
+	}
+	else
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+				 errmsg("datetime format is not dated and not timed")));
+	}
+
+	return (Datum) 0;
+}
+
 /*
  * do_to_timestamp: shared code for to_timestamp and to_date
  *
@@ -3911,7 +4180,8 @@ to_date(PG_FUNCTION_ARGS)
  */
 static void
 do_to_timestamp(text *date_txt, text *fmt, bool std,
-				struct pg_tm *tm, fsec_t *fsec, int *fprec)
+				struct pg_tm *tm, fsec_t *fsec, int *fprec,
+				uint32 *flags)
 {
 	FormatNode *format;
 	TmFromChar	tmfc;
@@ -3968,6 +4238,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 
 		pfree(fmt_str);
 
+		if (flags)
+			*flags = DCH_datetime_type(format);
+
 		if (!incache)
 			pfree(format);
 	}
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index bec129aff1c..bd15bfa5bb0 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -76,5 +76,8 @@ extern TimeTzADT *GetSQLCurrentTime(int32 typmod);
 extern TimeADT GetSQLLocalTime(int32 typmod);
 extern int	time2tm(TimeADT time, struct pg_tm *tm, fsec_t *fsec);
 extern int	timetz2tm(TimeTzADT *time, struct pg_tm *tm, fsec_t *fsec, int *tzp);
+extern int	tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
+extern int	tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
+extern void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
 
 #endif							/* DATE_H */
diff --git a/src/include/utils/formatting.h b/src/include/utils/formatting.h
index 0117144779e..beeaf10c332 100644
--- a/src/include/utils/formatting.h
+++ b/src/include/utils/formatting.h
@@ -26,4 +26,7 @@ extern char *asc_tolower(const char *buff, size_t nbytes);
 extern char *asc_toupper(const char *buff, size_t nbytes);
 extern char *asc_initcap(const char *buff, size_t nbytes);
 
+extern Datum parse_datetime(text *date_txt, text *fmt, bool std,
+							Oid *typid, int32 *typmod, int *tz);
+
 #endif
0004-standard-datetime-parsing-5.patchapplication/x-patch; name=0004-standard-datetime-parsing-5.patchDownload
commit 968a44c4f65ddc9bbad4e79156440814758301a4
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Mon Aug 26 04:40:24 2019 +0300

    Standard datetime parsing mode
    
    Reported-by:
    Bug:
    Discussion:
    Author:
    Reviewed-by:
    Tested-by:
    Backpatch-through:

diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 9ebcdad3cf9..142d9d2f678 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -99,11 +99,12 @@
 #include "utils/pg_locale.h"
 
 /* ----------
- * Routines type
+ * Routines flags
  * ----------
  */
-#define DCH_TYPE		1		/* DATE-TIME version	*/
-#define NUM_TYPE		2		/* NUMBER version	*/
+#define DCH_FLAG		0x1		/* DATE-TIME flag	*/
+#define NUM_FLAG		0x2		/* NUMBER flag	*/
+#define STD_FLAG		0x4		/* STANDARD flag	*/
 
 /* ----------
  * KeyWord Index (ascii from position 32 (' ') to 126 (~))
@@ -384,6 +385,7 @@ typedef struct
 {
 	FormatNode	format[DCH_CACHE_SIZE + 1];
 	char		str[DCH_CACHE_SIZE + 1];
+	bool		std;
 	bool		valid;
 	int			age;
 } DCHCacheEntry;
@@ -1008,11 +1010,12 @@ static const KeySuffix *suff_search(const char *str, const KeySuffix *suf, int t
 static bool is_separator_char(const char *str);
 static void NUMDesc_prepare(NUMDesc *num, FormatNode *n);
 static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
-						 const KeySuffix *suf, const int *index, int ver, NUMDesc *Num);
+						 const KeySuffix *suf, const int *index, uint32 flags, NUMDesc *Num);
 
 static void DCH_to_char(FormatNode *node, bool is_interval,
 						TmToChar *in, char *out, Oid collid);
-static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out);
+static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
+						  bool std);
 
 #ifdef DEBUG_TO_FROM_CHAR
 static void dump_index(const KeyWord *k, const int *index);
@@ -1030,7 +1033,7 @@ static int	from_char_parse_int_len(int *dest, char **src, const int len, FormatN
 static int	from_char_parse_int(int *dest, char **src, FormatNode *node);
 static int	seq_search(char *name, const char *const *array, int type, int max, int *len);
 static int	from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
-static void do_to_timestamp(text *date_txt, text *fmt,
+static void do_to_timestamp(text *date_txt, text *fmt, bool std,
 							struct pg_tm *tm, fsec_t *fsec, int *fprec);
 static char *fill_str(char *str, int c, int max);
 static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
@@ -1042,9 +1045,9 @@ static void NUM_numpart_to_char(NUMProc *Np, int id);
 static char *NUM_processor(FormatNode *node, NUMDesc *Num, char *inout,
 						   char *number, int input_len, int to_char_out_pre_spaces,
 						   int sign, bool is_to_char, Oid collid);
-static DCHCacheEntry *DCH_cache_getnew(const char *str);
-static DCHCacheEntry *DCH_cache_search(const char *str);
-static DCHCacheEntry *DCH_cache_fetch(const char *str);
+static DCHCacheEntry *DCH_cache_getnew(const char *str, bool std);
+static DCHCacheEntry *DCH_cache_search(const char *str, bool std);
+static DCHCacheEntry *DCH_cache_fetch(const char *str, bool std);
 static NUMCacheEntry *NUM_cache_getnew(const char *str);
 static NUMCacheEntry *NUM_cache_search(const char *str);
 static NUMCacheEntry *NUM_cache_fetch(const char *str);
@@ -1287,7 +1290,7 @@ NUMDesc_prepare(NUMDesc *num, FormatNode *n)
  */
 static void
 parse_format(FormatNode *node, const char *str, const KeyWord *kw,
-			 const KeySuffix *suf, const int *index, int ver, NUMDesc *Num)
+			 const KeySuffix *suf, const int *index, uint32 flags, NUMDesc *Num)
 {
 	FormatNode *n;
 
@@ -1305,7 +1308,7 @@ parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 		/*
 		 * Prefix
 		 */
-		if (ver == DCH_TYPE &&
+		if ((flags & DCH_FLAG) &&
 			(s = suff_search(str, suf, SUFFTYPE_PREFIX)) != NULL)
 		{
 			suffix |= s->id;
@@ -1326,13 +1329,13 @@ parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 			/*
 			 * NUM version: Prepare global NUMDesc struct
 			 */
-			if (ver == NUM_TYPE)
+			if (flags & NUM_FLAG)
 				NUMDesc_prepare(Num, n);
 
 			/*
 			 * Postfix
 			 */
-			if (ver == DCH_TYPE && *str &&
+			if ((flags & DCH_FLAG) && *str &&
 				(s = suff_search(str, suf, SUFFTYPE_POSTFIX)) != NULL)
 			{
 				n->suffix |= s->id;
@@ -1346,11 +1349,35 @@ parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 		{
 			int			chlen;
 
-			/*
-			 * Process double-quoted literal string, if any
-			 */
-			if (*str == '"')
+			if (flags & STD_FLAG)
+			{
+				/*
+				 * Standard mode, allow only following separators:
+				 * "-./,':; "
+				 */
+				if (strchr("-./,':; ", *str) == NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							 errmsg("invalid datetime format separator: \"%s\"",
+									pnstrdup(str, pg_mblen(str)))));
+
+				if (*str == ' ')
+					n->type = NODE_TYPE_SPACE;
+				else
+					n->type = NODE_TYPE_SEPARATOR;
+
+				n->character[0] = *str;
+				n->character[1] = '\0';
+				n->key = NULL;
+				n->suffix = 0;
+				n++;
+				str++;
+			}
+			else if (*str == '"')
 			{
+				/*
+				 * Process double-quoted literal string, if any
+				 */
 				str++;
 				while (*str)
 				{
@@ -1382,7 +1409,7 @@ parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 					str++;
 				chlen = pg_mblen(str);
 
-				if (ver == DCH_TYPE && is_separator_char(str))
+				if ((flags & DCH_FLAG) && is_separator_char(str))
 					n->type = NODE_TYPE_SEPARATOR;
 				else if (isspace((unsigned char) *str))
 					n->type = NODE_TYPE_SPACE;
@@ -3080,13 +3107,13 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
  * ----------
  */
 static void
-DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
+DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 {
 	FormatNode *n;
 	char	   *s;
 	int			len,
 				value;
-	bool		fx_mode = false;
+	bool		fx_mode = std;
 
 	/* number of extra skipped characters (more than given in format string) */
 	int			extra_skip = 0;
@@ -3109,7 +3136,23 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 
 		if (n->type == NODE_TYPE_SPACE || n->type == NODE_TYPE_SEPARATOR)
 		{
-			if (!fx_mode)
+			if (std)
+			{
+				/*
+				 * Standard mode requires strict matching between format string
+				 * separators/spaces and input string.
+				 */
+				Assert(n->character[0] && !n->character[1]);
+
+				if (*s == n->character[0])
+					s++;
+				else
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							 errmsg("unmatched format separator \"%c\"",
+									n->character[0])));
+			}
+			else if (!fx_mode)
 			{
 				/*
 				 * In non FX (fixed format) mode one format string space or
@@ -3461,6 +3504,27 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 			}
 		}
 	}
+
+	/*
+	 * Standard parsing mode doesn't allow unmatched format patterns or
+	 * trailing characters in the input string.
+	 */
+	if (std)
+	{
+		if (n->type != NODE_TYPE_END)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+					 errmsg("input string is too short for datetime format")));
+
+		while (*s != '\0' && isspace((unsigned char) *s))
+			s++;
+
+		if (*s != '\0')
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+					 errmsg("trailing characters remain in input string after "
+							"datetime format")));
+	}
 }
 
 /*
@@ -3483,7 +3547,7 @@ DCH_prevent_counter_overflow(void)
 
 /* select a DCHCacheEntry to hold the given format picture */
 static DCHCacheEntry *
-DCH_cache_getnew(const char *str)
+DCH_cache_getnew(const char *str, bool std)
 {
 	DCHCacheEntry *ent;
 
@@ -3533,6 +3597,7 @@ DCH_cache_getnew(const char *str)
 			MemoryContextAllocZero(TopMemoryContext, sizeof(DCHCacheEntry));
 		ent->valid = false;
 		StrNCpy(ent->str, str, DCH_CACHE_SIZE + 1);
+		ent->std = std;
 		ent->age = (++DCHCounter);
 		/* caller is expected to fill format, then set valid */
 		++n_DCHCache;
@@ -3542,7 +3607,7 @@ DCH_cache_getnew(const char *str)
 
 /* look for an existing DCHCacheEntry matching the given format picture */
 static DCHCacheEntry *
-DCH_cache_search(const char *str)
+DCH_cache_search(const char *str, bool std)
 {
 	/* Ensure we can advance DCHCounter below */
 	DCH_prevent_counter_overflow();
@@ -3551,7 +3616,7 @@ DCH_cache_search(const char *str)
 	{
 		DCHCacheEntry *ent = DCHCache[i];
 
-		if (ent->valid && strcmp(ent->str, str) == 0)
+		if (ent->valid && strcmp(ent->str, str) == 0 && ent->std == std)
 		{
 			ent->age = (++DCHCounter);
 			return ent;
@@ -3563,21 +3628,21 @@ DCH_cache_search(const char *str)
 
 /* Find or create a DCHCacheEntry for the given format picture */
 static DCHCacheEntry *
-DCH_cache_fetch(const char *str)
+DCH_cache_fetch(const char *str, bool std)
 {
 	DCHCacheEntry *ent;
 
-	if ((ent = DCH_cache_search(str)) == NULL)
+	if ((ent = DCH_cache_search(str, std)) == NULL)
 	{
 		/*
 		 * Not in the cache, must run parser and save a new format-picture to
 		 * the cache.  Do not mark the cache entry valid until parsing
 		 * succeeds.
 		 */
-		ent = DCH_cache_getnew(str);
+		ent = DCH_cache_getnew(str, std);
 
-		parse_format(ent->format, str, DCH_keywords,
-					 DCH_suff, DCH_index, DCH_TYPE, NULL);
+		parse_format(ent->format, str, DCH_keywords, DCH_suff, DCH_index,
+					 DCH_FLAG | (std ? STD_FLAG : 0), NULL);
 
 		ent->valid = true;
 	}
@@ -3622,14 +3687,14 @@ datetime_to_char_body(TmToChar *tmtc, text *fmt, bool is_interval, Oid collid)
 		format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode));
 
 		parse_format(format, fmt_str, DCH_keywords,
-					 DCH_suff, DCH_index, DCH_TYPE, NULL);
+					 DCH_suff, DCH_index, DCH_FLAG, NULL);
 	}
 	else
 	{
 		/*
 		 * Use cache buffers
 		 */
-		DCHCacheEntry *ent = DCH_cache_fetch(fmt_str);
+		DCHCacheEntry *ent = DCH_cache_fetch(fmt_str, false);
 
 		incache = true;
 		format = ent->format;
@@ -3771,7 +3836,7 @@ to_timestamp(PG_FUNCTION_ARGS)
 	fsec_t		fsec;
 	int			fprec;
 
-	do_to_timestamp(date_txt, fmt, &tm, &fsec, &fprec);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec);
 
 	/* Use the specified time zone, if any. */
 	if (tm.tm_zone)
@@ -3810,7 +3875,7 @@ to_date(PG_FUNCTION_ARGS)
 	struct pg_tm tm;
 	fsec_t		fsec;
 
-	do_to_timestamp(date_txt, fmt, &tm, &fsec, NULL);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL);
 
 	/* Prevent overflow in Julian-day routines */
 	if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3845,7 +3910,7 @@ to_date(PG_FUNCTION_ARGS)
  * struct 'tm' and 'fsec'.
  */
 static void
-do_to_timestamp(text *date_txt, text *fmt,
+do_to_timestamp(text *date_txt, text *fmt, bool std,
 				struct pg_tm *tm, fsec_t *fsec, int *fprec)
 {
 	FormatNode *format;
@@ -3880,15 +3945,15 @@ do_to_timestamp(text *date_txt, text *fmt,
 
 			format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode));
 
-			parse_format(format, fmt_str, DCH_keywords,
-						 DCH_suff, DCH_index, DCH_TYPE, NULL);
+			parse_format(format, fmt_str, DCH_keywords, DCH_suff, DCH_index,
+						 DCH_FLAG | (std ? STD_FLAG : 0), NULL);
 		}
 		else
 		{
 			/*
 			 * Use cache buffers
 			 */
-			DCHCacheEntry *ent = DCH_cache_fetch(fmt_str);
+			DCHCacheEntry *ent = DCH_cache_fetch(fmt_str, std);
 
 			incache = true;
 			format = ent->format;
@@ -3899,7 +3964,7 @@ do_to_timestamp(text *date_txt, text *fmt,
 		/* dump_index(DCH_keywords, DCH_index); */
 #endif
 
-		DCH_from_char(format, date_str, &tmfc);
+		DCH_from_char(format, date_str, &tmfc, std);
 
 		pfree(fmt_str);
 
@@ -4268,7 +4333,7 @@ NUM_cache_fetch(const char *str)
 		zeroize_NUM(&ent->Num);
 
 		parse_format(ent->format, str, NUM_keywords,
-					 NULL, NUM_index, NUM_TYPE, &ent->Num);
+					 NULL, NUM_index, NUM_FLAG, &ent->Num);
 
 		ent->valid = true;
 	}
@@ -4300,7 +4365,7 @@ NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree)
 		zeroize_NUM(Num);
 
 		parse_format(format, str, NUM_keywords,
-					 NULL, NUM_index, NUM_TYPE, Num);
+					 NULL, NUM_index, NUM_FLAG, Num);
 	}
 	else
 	{
0006-error-suppression-for-datetime-5.patchapplication/x-patch; name=0006-error-suppression-for-datetime-5.patchDownload
commit 4d0bb7bea22be75533c3445a618c671b330ec63c
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Sun Jul 21 01:14:48 2019 +0300

    Error suppression support for upcoming jsonpath .datetime() method
    
    Add support of error suppression in some date and time manipulation functions
    as it's required for jsonpath .datetime() method support.  This commit doesn't
    use PG_TRY()/PG_CATCH() in order to implement that.  Instead, it provides
    internal versions of date and time functions used, which support error
    suppression.
    
    Discussion: https://postgr.es/m/CAPpHfdsZgYEra_PeCLGNoXOWYx6iU-S3wF8aX0ObQUcZU%2B4XTw%40mail.gmail.com
    Author: Alexander Korotkov, Nikita Glukhov
    Reviewed-by: Anastasia Lubennikova

diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 9e291b5c7bc..fa50d79c05d 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -550,13 +550,15 @@ date_mii(PG_FUNCTION_ARGS)
 	PG_RETURN_DATEADT(result);
 }
 
+
 /*
- * Internal routines for promoting date to timestamp and timestamp with
- * time zone
+ * Promote date to timestamp.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero is returned.
  */
-
-static Timestamp
-date2timestamp(DateADT dateVal)
+Timestamp
+date2timestamp_opt_error(DateADT dateVal, bool *have_error)
 {
 	Timestamp	result;
 
@@ -572,9 +574,19 @@ date2timestamp(DateADT dateVal)
 		 * boundary need be checked for overflow.
 		 */
 		if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("date out of range for timestamp")));
+		{
+			if (have_error)
+			{
+				*have_error = true;
+				return (Timestamp) 0;
+			}
+			else
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("date out of range for timestamp")));
+			}
+		}
 
 		/* date is days since 2000, timestamp is microseconds since same... */
 		result = dateVal * USECS_PER_DAY;
@@ -583,8 +595,23 @@ date2timestamp(DateADT dateVal)
 	return result;
 }
 
+/*
+ * Single-argument version of date2timestamp_opt_error().
+ */
 static TimestampTz
-date2timestamptz(DateADT dateVal)
+date2timestamp(DateADT dateVal)
+{
+	return date2timestamp_opt_error(dateVal, NULL);
+}
+
+/*
+ * Promote date to timestamp with time zone.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero is returned.
+ */
+TimestampTz
+date2timestamptz_opt_error(DateADT dateVal, bool *have_error)
 {
 	TimestampTz result;
 	struct pg_tm tt,
@@ -603,9 +630,19 @@ date2timestamptz(DateADT dateVal)
 		 * boundary need be checked for overflow.
 		 */
 		if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("date out of range for timestamp")));
+		{
+			if (have_error)
+			{
+				*have_error = true;
+				return (TimestampTz) 0;
+			}
+			else
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("date out of range for timestamp")));
+			}
+		}
 
 		j2date(dateVal + POSTGRES_EPOCH_JDATE,
 			   &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday));
@@ -621,14 +658,33 @@ date2timestamptz(DateADT dateVal)
 		 * of time zone, check for allowed timestamp range after adding tz.
 		 */
 		if (!IS_VALID_TIMESTAMP(result))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("date out of range for timestamp")));
+		{
+			if (have_error)
+			{
+				*have_error = true;
+				return (TimestampTz) 0;
+			}
+			else
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("date out of range for timestamp")));
+			}
+		}
 	}
 
 	return result;
 }
 
+/*
+ * Single-argument version of date2timestamptz_opt_error().
+ */
+static TimestampTz
+date2timestamptz(DateADT dateVal)
+{
+	return date2timestamptz_opt_error(dateVal, NULL);
+}
+
 /*
  * date2timestamp_no_overflow
  *
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index deadd7bd14e..86012c4bba8 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -98,6 +98,43 @@
 #include "utils/numeric.h"
 #include "utils/pg_locale.h"
 
+/* ----------
+ * Convenience macros for error handling
+ * ----------
+ *
+ * Two macros below help to handle errors in functions that take
+ * 'bool *have_error' argument.  When this argument is not NULL, it's expected
+ * that function will suppress ereports when possible.  Instead it should
+ * return some default value and set *have_error flag.
+ *
+ * RETURN_ERROR() macro intended to wrap ereport() calls.  When have_error
+ * function argument is not NULL, then instead of ereport'ing we set
+ * *have_error flag and go to on_error label.  It's supposed that jump
+ * resources will be freed and some 'default' value returned.
+ *
+ * CHECK_ERROR() jumps on_error label when *have_error flag is defined and set.
+ * It's supposed to be used for immediate exit from the function on error
+ * after call of another function with 'bool *have_error' argument.
+ */
+#define RETURN_ERROR(throw_error) \
+do { \
+	if (have_error) \
+	{ \
+		*have_error = true; \
+		goto on_error; \
+	} \
+	else \
+	{ \
+		throw_error; \
+	} \
+} while (0)
+
+#define CHECK_ERROR \
+do { \
+	if (have_error && *have_error) \
+		goto on_error; \
+} while (0)
+
 /* ----------
  * Routines flags
  * ----------
@@ -1020,7 +1057,7 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 static void DCH_to_char(FormatNode *node, bool is_interval,
 						TmToChar *in, char *out, Oid collid);
 static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
-						  bool std);
+						  bool std, bool *have_error);
 
 #ifdef DEBUG_TO_FROM_CHAR
 static void dump_index(const KeyWord *k, const int *index);
@@ -1032,15 +1069,21 @@ static char *str_numth(char *dest, char *num, int type);
 static int	adjust_partial_year_to_2020(int year, int ndigits);
 static int	adjust_relative_partial_year_to_2020(int year);
 static int	strspace_len(char *str);
-static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode);
-static void from_char_set_int(int *dest, const int value, const FormatNode *node);
-static int	from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node);
-static int	from_char_parse_int(int *dest, char **src, FormatNode *node);
+static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode,
+							   bool *have_error);
+static void from_char_set_int(int *dest, const int value, const FormatNode *node,
+							  bool *have_error);
+static int	from_char_parse_int_len(int *dest, char **src, const int len,
+									FormatNode *node, bool *have_error);
+static int	from_char_parse_int(int *dest, char **src, FormatNode *node,
+								bool *have_error);
 static int	seq_search(char *name, const char *const *array, int type, int max, int *len);
-static int	from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
+static int	from_char_seq_search(int *dest, char **src,
+								 const char *const *array, int type, int max,
+								 FormatNode *node, bool *have_error);
 static void do_to_timestamp(text *date_txt, text *fmt, bool std,
 							struct pg_tm *tm, fsec_t *fsec, int *fprec,
-							uint32 *flags);
+							uint32 *flags, bool *have_error);
 static char *fill_str(char *str, int c, int max);
 static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
 static char *int_to_roman(int number);
@@ -2252,21 +2295,26 @@ strspace_len(char *str)
  *
  * Puke if the date mode has already been set, and the caller attempts to set
  * it to a conflicting mode.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
-from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode)
+from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode, bool *have_error)
 {
 	if (mode != FROM_CHAR_DATE_NONE)
 	{
 		if (tmfc->mode == FROM_CHAR_DATE_NONE)
 			tmfc->mode = mode;
 		else if (tmfc->mode != mode)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("invalid combination of date conventions"),
-					 errhint("Do not mix Gregorian and ISO week date "
-							 "conventions in a formatting template.")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("invalid combination of date conventions"),
+								  errhint("Do not mix Gregorian and ISO week date "
+										  "conventions in a formatting template."))));
 	}
+
+on_error:
+	return;
 }
 
 /*
@@ -2274,18 +2322,25 @@ from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode)
  *
  * Puke if the destination integer has previously been set to some other
  * non-zero value.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
-from_char_set_int(int *dest, const int value, const FormatNode *node)
+from_char_set_int(int *dest, const int value, const FormatNode *node,
+				  bool *have_error)
 {
 	if (*dest != 0 && *dest != value)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("conflicting values for \"%s\" field in formatting string",
-						node->key->name),
-				 errdetail("This value contradicts a previous setting for "
-						   "the same field type.")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("conflicting values for \"%s\" field in "
+									 "formatting string",
+									 node->key->name),
+							  errdetail("This value contradicts a previous setting "
+										"for the same field type."))));
 	*dest = value;
+
+on_error:
+	return;
 }
 
 /*
@@ -2307,9 +2362,13 @@ from_char_set_int(int *dest, const int value, const FormatNode *node)
  * Note that from_char_parse_int() provides a more convenient wrapper where
  * the length of the field is the same as the length of the format keyword (as
  * with DD and MI).
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and -1 is returned.
  */
 static int
-from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
+from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node,
+						bool *have_error)
 {
 	long		result;
 	char		copy[DCH_MAX_ITEM_SIZ + 1];
@@ -2342,51 +2401,60 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
 		char	   *last;
 
 		if (used < len)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("source string too short for \"%s\" formatting field",
-							node->key->name),
-					 errdetail("Field requires %d characters, but only %d "
-							   "remain.",
-							   len, used),
-					 errhint("If your source string is not fixed-width, try "
-							 "using the \"FM\" modifier.")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("source string too short for \"%s\" "
+										 "formatting field",
+										 node->key->name),
+								  errdetail("Field requires %d characters, "
+											"but only %d remain.",
+											len, used),
+								  errhint("If your source string is not fixed-width, "
+										  "try using the \"FM\" modifier."))));
 
 		errno = 0;
 		result = strtol(copy, &last, 10);
 		used = last - copy;
 
 		if (used > 0 && used < len)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("invalid value \"%s\" for \"%s\"",
-							copy, node->key->name),
-					 errdetail("Field requires %d characters, but only %d "
-							   "could be parsed.", len, used),
-					 errhint("If your source string is not fixed-width, try "
-							 "using the \"FM\" modifier.")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("invalid value \"%s\" for \"%s\"",
+										 copy, node->key->name),
+								  errdetail("Field requires %d characters, "
+											"but only %d could be parsed.",
+											len, used),
+								  errhint("If your source string is not fixed-width, "
+										  "try using the \"FM\" modifier."))));
 
 		*src += used;
 	}
 
 	if (*src == init)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("invalid value \"%s\" for \"%s\"",
-						copy, node->key->name),
-				 errdetail("Value must be an integer.")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("invalid value \"%s\" for \"%s\"",
+									 copy, node->key->name),
+							  errdetail("Value must be an integer."))));
 
 	if (errno == ERANGE || result < INT_MIN || result > INT_MAX)
-		ereport(ERROR,
-				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-				 errmsg("value for \"%s\" in source string is out of range",
-						node->key->name),
-				 errdetail("Value must be in the range %d to %d.",
-						   INT_MIN, INT_MAX)));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							  errmsg("value for \"%s\" in source string is out of range",
+									 node->key->name),
+							  errdetail("Value must be in the range %d to %d.",
+										INT_MIN, INT_MAX))));
 
 	if (dest != NULL)
-		from_char_set_int(dest, (int) result, node);
+	{
+		from_char_set_int(dest, (int) result, node, have_error);
+		CHECK_ERROR;
+	}
+
 	return *src - init;
+
+on_error:
+	return -1;
 }
 
 /*
@@ -2399,9 +2467,9 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
  * required length explicitly.
  */
 static int
-from_char_parse_int(int *dest, char **src, FormatNode *node)
+from_char_parse_int(int *dest, char **src, FormatNode *node, bool *have_error)
 {
-	return from_char_parse_int_len(dest, src, node->key->len, node);
+	return from_char_parse_int_len(dest, src, node->key->len, node, have_error);
 }
 
 /* ----------
@@ -2484,11 +2552,12 @@ seq_search(char *name, const char *const *array, int type, int max, int *len)
  * pointed to by 'dest', advance 'src' to the end of the part of the string
  * which matched, and return the number of characters consumed.
  *
- * If the string doesn't match, throw an error.
+ * If the string doesn't match, throw an error if 'have_error' is NULL,
+ * otherwise set '*have_error' and return -1.
  */
 static int
-from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max,
-					 FormatNode *node)
+from_char_seq_search(int *dest, char **src, const char *const *array, int type,
+					 int max, FormatNode *node, bool *have_error)
 {
 	int			len;
 
@@ -2500,15 +2569,18 @@ from_char_seq_search(int *dest, char **src, const char *const *array, int type,
 		Assert(max <= DCH_MAX_ITEM_SIZ);
 		strlcpy(copy, *src, max + 1);
 
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("invalid value \"%s\" for \"%s\"",
-						copy, node->key->name),
-				 errdetail("The given value did not match any of the allowed "
-						   "values for this field.")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("invalid value \"%s\" for \"%s\"",
+									 copy, node->key->name),
+							  errdetail("The given value did not match any of "
+										"the allowed values for this field."))));
 	}
 	*src += len;
 	return len;
+
+on_error:
+	return -1;
 }
 
 /* ----------
@@ -3110,10 +3182,13 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
  *
  * Note: we currently don't have any to_interval() function, so there
  * is no need here for INVALID_FOR_INTERVAL checks.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  * ----------
  */
 static void
-DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
+DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std,
+			  bool *have_error)
 {
 	FormatNode *n;
 	char	   *s;
@@ -3153,10 +3228,10 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 				if (*s == n->character[0])
 					s++;
 				else
-					ereport(ERROR,
-							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-							 errmsg("unmatched format separator \"%c\"",
-									n->character[0])));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+										  errmsg("unmatched format separator \"%c\"",
+												 n->character[0]))));
 			}
 			else if (!fx_mode)
 			{
@@ -3212,7 +3287,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 			continue;
 		}
 
-		from_char_set_mode(out, n->key->date_mode);
+		from_char_set_mode(out, n->key->date_mode, have_error);
+		CHECK_ERROR;
 
 		switch (n->key->id)
 		{
@@ -3224,8 +3300,10 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 			case DCH_a_m:
 			case DCH_p_m:
 				from_char_seq_search(&value, &s, ampm_strings_long,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->pm, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->pm, value % 2, n, have_error);
+				CHECK_ERROR;
 				out->clock = CLOCK_12_HOUR;
 				break;
 			case DCH_AM:
@@ -3233,30 +3311,37 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 			case DCH_am:
 			case DCH_pm:
 				from_char_seq_search(&value, &s, ampm_strings,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->pm, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->pm, value % 2, n, have_error);
+				CHECK_ERROR;
 				out->clock = CLOCK_12_HOUR;
 				break;
 			case DCH_HH:
 			case DCH_HH12:
-				from_char_parse_int_len(&out->hh, &s, 2, n);
+				from_char_parse_int_len(&out->hh, &s, 2, n, have_error);
+				CHECK_ERROR;
 				out->clock = CLOCK_12_HOUR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_HH24:
-				from_char_parse_int_len(&out->hh, &s, 2, n);
+				from_char_parse_int_len(&out->hh, &s, 2, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_MI:
-				from_char_parse_int(&out->mi, &s, n);
+				from_char_parse_int(&out->mi, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_SS:
-				from_char_parse_int(&out->ss, &s, n);
+				from_char_parse_int(&out->ss, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_MS:		/* millisecond */
-				len = from_char_parse_int_len(&out->ms, &s, 3, n);
+				len = from_char_parse_int_len(&out->ms, &s, 3, n, have_error);
+				CHECK_ERROR;
 
 				/*
 				 * 25 is 0.25 and 250 is 0.25 too; 025 is 0.025 and not 0.25
@@ -3277,7 +3362,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 			case DCH_US:		/* microsecond */
 				len = from_char_parse_int_len(&out->us, &s,
 											  n->key->id == DCH_US ? 6 :
-											  out->ff, n);
+											  out->ff, n, have_error);
+				CHECK_ERROR;
 
 				out->us *= len == 1 ? 100000 :
 					len == 2 ? 10000 :
@@ -3288,16 +3374,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_SSSS:
-				from_char_parse_int(&out->ssss, &s, n);
+				from_char_parse_int(&out->ssss, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_tz:
 			case DCH_TZ:
 			case DCH_OF:
-				ereport(ERROR,
-						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						 errmsg("formatting field \"%s\" is only supported in to_char",
-								n->key->name)));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									  errmsg("formatting field \"%s\" is only supported in to_char",
+											 n->key->name))));
+				CHECK_ERROR;
 				break;
 			case DCH_TZH:
 
@@ -3321,82 +3409,102 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 						out->tzsign = +1;
 				}
 
-				from_char_parse_int_len(&out->tzh, &s, 2, n);
+				from_char_parse_int_len(&out->tzh, &s, 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_TZM:
 				/* assign positive timezone sign if TZH was not seen before */
 				if (!out->tzsign)
 					out->tzsign = +1;
-				from_char_parse_int_len(&out->tzm, &s, 2, n);
+				from_char_parse_int_len(&out->tzm, &s, 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_A_D:
 			case DCH_B_C:
 			case DCH_a_d:
 			case DCH_b_c:
 				from_char_seq_search(&value, &s, adbc_strings_long,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->bc, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->bc, value % 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_AD:
 			case DCH_BC:
 			case DCH_ad:
 			case DCH_bc:
 				from_char_seq_search(&value, &s, adbc_strings,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->bc, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->bc, value % 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_MONTH:
 			case DCH_Month:
 			case DCH_month:
 				from_char_seq_search(&value, &s, months_full, ONE_UPPER,
-									 MAX_MONTH_LEN, n);
-				from_char_set_int(&out->mm, value + 1, n);
+									 MAX_MONTH_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, value + 1, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_MON:
 			case DCH_Mon:
 			case DCH_mon:
 				from_char_seq_search(&value, &s, months, ONE_UPPER,
-									 MAX_MON_LEN, n);
-				from_char_set_int(&out->mm, value + 1, n);
+									 MAX_MON_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, value + 1, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_MM:
-				from_char_parse_int(&out->mm, &s, n);
+				from_char_parse_int(&out->mm, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_DAY:
 			case DCH_Day:
 			case DCH_day:
 				from_char_seq_search(&value, &s, days, ONE_UPPER,
-									 MAX_DAY_LEN, n);
-				from_char_set_int(&out->d, value, n);
+									 MAX_DAY_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->d, value, n, have_error);
+				CHECK_ERROR;
 				out->d++;
 				break;
 			case DCH_DY:
 			case DCH_Dy:
 			case DCH_dy:
 				from_char_seq_search(&value, &s, days, ONE_UPPER,
-									 MAX_DY_LEN, n);
-				from_char_set_int(&out->d, value, n);
+									 MAX_DY_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->d, value, n, have_error);
+				CHECK_ERROR;
 				out->d++;
 				break;
 			case DCH_DDD:
-				from_char_parse_int(&out->ddd, &s, n);
+				from_char_parse_int(&out->ddd, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_IDDD:
-				from_char_parse_int_len(&out->ddd, &s, 3, n);
+				from_char_parse_int_len(&out->ddd, &s, 3, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_DD:
-				from_char_parse_int(&out->dd, &s, n);
+				from_char_parse_int(&out->dd, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_D:
-				from_char_parse_int(&out->d, &s, n);
+				from_char_parse_int(&out->d, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_ID:
-				from_char_parse_int_len(&out->d, &s, 1, n);
+				from_char_parse_int_len(&out->d, &s, 1, n, have_error);
+				CHECK_ERROR;
 				/* Shift numbering to match Gregorian where Sunday = 1 */
 				if (++out->d > 7)
 					out->d = 1;
@@ -3404,7 +3512,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 				break;
 			case DCH_WW:
 			case DCH_IW:
-				from_char_parse_int(&out->ww, &s, n);
+				from_char_parse_int(&out->ww, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Q:
@@ -3419,11 +3528,13 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 				 * We still parse the source string for an integer, but it
 				 * isn't stored anywhere in 'out'.
 				 */
-				from_char_parse_int((int *) NULL, &s, n);
+				from_char_parse_int((int *) NULL, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_CC:
-				from_char_parse_int(&out->cc, &s, n);
+				from_char_parse_int(&out->cc, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Y_YYY:
@@ -3435,11 +3546,12 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 
 					matched = sscanf(s, "%d,%03d%n", &millennia, &years, &nch);
 					if (matched < 2)
-						ereport(ERROR,
-								(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-								 errmsg("invalid input string for \"Y,YYY\"")));
+						RETURN_ERROR(ereport(ERROR,
+											 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+											  errmsg("invalid input string for \"Y,YYY\""))));
 					years += (millennia * 1000);
-					from_char_set_int(&out->year, years, n);
+					from_char_set_int(&out->year, years, n, have_error);
+					CHECK_ERROR;
 					out->yysz = 4;
 					s += nch;
 					SKIP_THth(s, n->suffix);
@@ -3448,53 +3560,66 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 			case DCH_RRRR:
 			case DCH_YYYY:
 			case DCH_IYYY:
-				from_char_parse_int(&out->year, &s, n);
+				from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
 				out->yysz = 4;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_YYY:
 			case DCH_IYY:
-				len = from_char_parse_int(&out->year, &s, n);
+				len = from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
 				out->year = adjust_partial_year_to_2020(out->year, len);
 				out->yysz = 3;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_RR:
-				from_char_parse_int(&out->year, &s, n);
+				from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
 				out->year = adjust_relative_partial_year_to_2020(out->year);
 				out->yysz = 2;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_YY:
 			case DCH_IY:
-				len = from_char_parse_int(&out->year, &s, n);
+				len = from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
 				out->year = adjust_partial_year_to_2020(out->year, len);
 				out->yysz = 2;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Y:
 			case DCH_I:
-				len = from_char_parse_int(&out->year, &s, n);
+				len = from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
 				out->year = adjust_partial_year_to_2020(out->year, len);
 				out->yysz = 1;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_RM:
 				from_char_seq_search(&value, &s, rm_months_upper,
-									 ALL_UPPER, MAX_RM_LEN, n);
-				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n);
+									 ALL_UPPER, MAX_RM_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value,
+								  n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_rm:
 				from_char_seq_search(&value, &s, rm_months_lower,
-									 ALL_LOWER, MAX_RM_LEN, n);
-				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n);
+									 ALL_LOWER, MAX_RM_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value,
+								  n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_W:
-				from_char_parse_int(&out->w, &s, n);
+				from_char_parse_int(&out->w, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_J:
-				from_char_parse_int(&out->j, &s, n);
+				from_char_parse_int(&out->j, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 		}
@@ -3518,19 +3643,22 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 	if (std)
 	{
 		if (n->type != NODE_TYPE_END)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("input string is too short for datetime format")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("input string is too short for datetime format"))));
 
 		while (*s != '\0' && isspace((unsigned char) *s))
 			s++;
 
 		if (*s != '\0')
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("trailing characters remain in input string after "
-							"datetime format")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("trailing characters remain in input string "
+										 "after datetime format"))));
 	}
+
+on_error:
+	return;
 }
 
 /*
@@ -3551,9 +3679,13 @@ DCH_prevent_counter_overflow(void)
 	}
 }
 
-/* Get mask of date/time/zone components present in format nodes. */
+/*
+ * Get mask of date/time/zone components present in format nodes.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
+ */
 static int
-DCH_datetime_type(FormatNode *node)
+DCH_datetime_type(FormatNode *node, bool *have_error)
 {
 	FormatNode *n;
 	int			flags = 0;
@@ -3594,10 +3726,10 @@ DCH_datetime_type(FormatNode *node)
 			case DCH_tz:
 			case DCH_TZ:
 			case DCH_OF:
-				ereport(ERROR,
-						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						 errmsg("formatting field \"%s\" is only supported in to_char",
-								n->key->name)));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									  errmsg("formatting field \"%s\" is only supported in to_char",
+											 n->key->name))));
 				flags |= DCH_ZONED;
 				break;
 			case DCH_TZH:
@@ -3653,6 +3785,7 @@ DCH_datetime_type(FormatNode *node)
 		}
 	}
 
+on_error:
 	return flags;
 }
 
@@ -3947,7 +4080,7 @@ to_timestamp(PG_FUNCTION_ARGS)
 	fsec_t		fsec;
 	int			fprec;
 
-	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL, NULL);
 
 	/* Use the specified time zone, if any. */
 	if (tm.tm_zone)
@@ -3986,7 +4119,7 @@ to_date(PG_FUNCTION_ARGS)
 	struct pg_tm tm;
 	fsec_t		fsec;
 
-	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL, NULL);
 
 	/* Prevent overflow in Julian-day routines */
 	if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -4013,17 +4146,21 @@ to_date(PG_FUNCTION_ARGS)
  * the presence of date/time/zone components in the format string.
  *
  * When timezone component is present, the corresponding offset is set to '*tz'.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero value is returned.
  */
 Datum
 parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
-			   int32 *typmod, int *tz)
+			   int32 *typmod, int *tz, bool *have_error)
 {
 	struct pg_tm tm;
 	fsec_t		fsec;
 	int			fprec = 0;
 	uint32		flags;
 
-	do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags);
+	do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags, have_error);
+	CHECK_ERROR;
 
 	*typmod = fprec ? fprec : -1;	/* fractional part precision */
 
@@ -4044,16 +4181,22 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 				}
 				else
 				{
-					if (*tz == PG_INT32_MIN)
-						ereport(ERROR,
-								(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-								 errmsg("missing time zone in input string for type timestamptz")));
+					/*
+					 * Time zone is present in format string, but not in input
+					 * string.  Assuming do_to_timestamp() triggers no error
+					 * this should be possible only in non-strict case.
+					 */
+					Assert(!strict);
+
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+										  errmsg("missing time zone in input string for type timestamptz"))));
 				}
 
 				if (tm2timestamp(&tm, fsec, tz, &result) != 0)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("timestamptz out of range")));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("timestamptz out of range"))));
 
 				AdjustTimestampForTypmod(&result, *typmod);
 
@@ -4065,9 +4208,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 				Timestamp	result;
 
 				if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("timestamp out of range")));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("timestamp out of range"))));
 
 				AdjustTimestampForTypmod(&result, *typmod);
 
@@ -4079,9 +4222,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 		{
 			if (flags & DCH_ZONED)
 			{
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-						 errmsg("datetime format is zoned but not timed")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+									  errmsg("datetime format is zoned but not timed"))));
 			}
 			else
 			{
@@ -4089,20 +4232,20 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 
 				/* Prevent overflow in Julian-day routines */
 				if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("date out of range: \"%s\"",
-									text_to_cstring(date_txt))));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("date out of range: \"%s\"",
+												 text_to_cstring(date_txt)))));
 
 				result = 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(result))
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("date out of range: \"%s\"",
-									text_to_cstring(date_txt))));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("date out of range: \"%s\"",
+												 text_to_cstring(date_txt)))));
 
 				*typid = DATEOID;
 				return DateADTGetDatum(result);
@@ -4120,20 +4263,26 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 				int			dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
 
 				if (dterr)
-					DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz");
+					RETURN_ERROR(DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz"));
 			}
 			else
 			{
-				if (*tz == PG_INT32_MIN)
-					ereport(ERROR,
-							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-							 errmsg("missing time zone in input string for type timetz")));
+				/*
+				 * Time zone is present in format string, but not in input
+				 * string.  Assuming do_to_timestamp() triggers no error this
+				 * should be possible only in non-strict case.
+				 */
+				Assert(!strict);
+
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+									  errmsg("missing time zone in input string for type timetz"))));
 			}
 
 			if (tm2timetz(&tm, fsec, *tz, result) != 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-						 errmsg("timetz out of range")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+									  errmsg("timetz out of range"))));
 
 			AdjustTimeForTypmod(&result->time, *typmod);
 
@@ -4145,9 +4294,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 			TimeADT		result;
 
 			if (tm2time(&tm, fsec, &result) != 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-						 errmsg("time out of range")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+									  errmsg("time out of range"))));
 
 			AdjustTimeForTypmod(&result, *typmod);
 
@@ -4157,11 +4306,12 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 	}
 	else
 	{
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("datetime format is not dated and not timed")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("datetime format is not dated and not timed"))));
 	}
 
+on_error:
 	return (Datum) 0;
 }
 
@@ -4177,17 +4327,24 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
  *
  * The TmFromChar is then analysed and converted into the final results in
  * struct 'tm' and 'fsec'.
+ *
+ * Bit mask of date/time/zone components found in 'fmt' is returned in 'flags'.
+ *
+ * 'std' specifies standard parsing mode.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
 do_to_timestamp(text *date_txt, text *fmt, bool std,
 				struct pg_tm *tm, fsec_t *fsec, int *fprec,
-				uint32 *flags)
+				uint32 *flags, bool *have_error)
 {
-	FormatNode *format;
+	FormatNode *format = NULL;
 	TmFromChar	tmfc;
 	int			fmt_len;
 	char	   *date_str;
 	int			fmask;
+	bool		incache = false;
 
 	date_str = text_to_cstring(date_txt);
 
@@ -4201,7 +4358,6 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 	if (fmt_len)
 	{
 		char	   *fmt_str;
-		bool		incache;
 
 		fmt_str = text_to_cstring(fmt);
 
@@ -4211,8 +4367,6 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 			 * Allocate new memory if format picture is bigger than static
 			 * cache and do not use cache (call parser always)
 			 */
-			incache = false;
-
 			format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode));
 
 			parse_format(format, fmt_str, DCH_keywords, DCH_suff, DCH_index,
@@ -4234,15 +4388,21 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 		/* dump_index(DCH_keywords, DCH_index); */
 #endif
 
-		DCH_from_char(format, date_str, &tmfc, std);
+		DCH_from_char(format, date_str, &tmfc, std, have_error);
+		CHECK_ERROR;
 
 		pfree(fmt_str);
 
 		if (flags)
-			*flags = DCH_datetime_type(format);
+			*flags = DCH_datetime_type(format, have_error);
 
 		if (!incache)
+		{
 			pfree(format);
+			format = NULL;
+		}
+
+		CHECK_ERROR;
 	}
 
 	DEBUG_TMFC(&tmfc);
@@ -4271,11 +4431,13 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 	if (tmfc.clock == CLOCK_12_HOUR)
 	{
 		if (tm->tm_hour < 1 || tm->tm_hour > HOURS_PER_DAY / 2)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("hour \"%d\" is invalid for the 12-hour clock",
-							tm->tm_hour),
-					 errhint("Use the 24-hour clock, or give an hour between 1 and 12.")));
+		{
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("hour \"%d\" is invalid for the 12-hour clock",
+										 tm->tm_hour),
+								  errhint("Use the 24-hour clock, or give an hour between 1 and 12."))));
+		}
 
 		if (tmfc.pm && tm->tm_hour < HOURS_PER_DAY / 2)
 			tm->tm_hour += HOURS_PER_DAY / 2;
@@ -4379,9 +4541,11 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 		 */
 
 		if (!tm->tm_year && !tmfc.bc)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("cannot calculate day of year without year information")));
+		{
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("cannot calculate day of year without year information"))));
+		}
 
 		if (tmfc.mode == FROM_CHAR_DATE_ISOWEEK)
 		{
@@ -4438,7 +4602,7 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 			 * said DTERR_MD_FIELD_OVERFLOW, because we don't want to print an
 			 * irrelevant hint about datestyle.
 			 */
-			DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp");
+			RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp"));
 		}
 	}
 
@@ -4447,7 +4611,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 		tm->tm_min < 0 || tm->tm_min >= MINS_PER_HOUR ||
 		tm->tm_sec < 0 || tm->tm_sec >= SECS_PER_MINUTE ||
 		*fsec < INT64CONST(0) || *fsec >= USECS_PER_SEC)
-		DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp");
+	{
+		RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp"));
+	}
 
 	/* Save parsed time-zone into tm->tm_zone if it was specified */
 	if (tmfc.tzsign)
@@ -4456,7 +4622,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 
 		if (tmfc.tzh < 0 || tmfc.tzh > MAX_TZDISP_HOUR ||
 			tmfc.tzm < 0 || tmfc.tzm >= MINS_PER_HOUR)
-			DateTimeParseError(DTERR_TZDISP_OVERFLOW, date_str, "timestamp");
+		{
+			RETURN_ERROR(DateTimeParseError(DTERR_TZDISP_OVERFLOW, date_str, "timestamp"));
+		}
 
 		tz = psprintf("%c%02d:%02d",
 					  tmfc.tzsign > 0 ? '+' : '-', tmfc.tzh, tmfc.tzm);
@@ -4466,6 +4634,11 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 
 	DEBUG_TM(tm);
 
+on_error:
+
+	if (format && !incache)
+		pfree(format);
+
 	pfree(date_str);
 }
 
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 2931bd58c0d..84bc97d40c3 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -329,11 +329,11 @@ timestamp_scale(PG_FUNCTION_ARGS)
 }
 
 /*
- * AdjustTimestampForTypmod --- round off a timestamp to suit given typmod
+ * AdjustTimestampForTypmodError --- round off a timestamp to suit given typmod
  * Works for either timestamp or timestamptz.
  */
-void
-AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
+bool
+AdjustTimestampForTypmodError(Timestamp *time, int32 typmod, bool *error)
 {
 	static const int64 TimestampScales[MAX_TIMESTAMP_PRECISION + 1] = {
 		INT64CONST(1000000),
@@ -359,10 +359,18 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
 		&& (typmod != -1) && (typmod != MAX_TIMESTAMP_PRECISION))
 	{
 		if (typmod < 0 || typmod > MAX_TIMESTAMP_PRECISION)
+		{
+			if (error)
+			{
+				*error = true;
+				return false;
+			}
+
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("timestamp(%d) precision must be between %d and %d",
 							typmod, 0, MAX_TIMESTAMP_PRECISION)));
+		}
 
 		if (*time >= INT64CONST(0))
 		{
@@ -375,8 +383,15 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
 					  * TimestampScales[typmod]);
 		}
 	}
+
+	return true;
 }
 
+void
+AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
+{
+	(void) AdjustTimestampForTypmodError(time, typmod, NULL);
+}
 
 /* timestamptz_in()
  * Convert a string to internal form.
@@ -5172,8 +5187,15 @@ timestamp_timestamptz(PG_FUNCTION_ARGS)
 	PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
 }
 
-static TimestampTz
-timestamp2timestamptz(Timestamp timestamp)
+/*
+ * Convert timestamp to timestamp with time zone.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero is returned.
+ */
+
+TimestampTz
+timestamp2timestamptz_opt_error(Timestamp timestamp, bool *have_error)
 {
 	TimestampTz result;
 	struct pg_tm tt,
@@ -5182,23 +5204,33 @@ timestamp2timestamptz(Timestamp timestamp)
 	int			tz;
 
 	if (TIMESTAMP_NOT_FINITE(timestamp))
-		result = timestamp;
-	else
-	{
-		if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("timestamp out of range")));
+		return timestamp;
 
+	if (!timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL))
+	{
 		tz = DetermineTimeZoneOffset(tm, session_timezone);
 
-		if (tm2timestamp(tm, fsec, &tz, &result) != 0)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("timestamp out of range")));
+		if (!tm2timestamp(tm, fsec, &tz, &result))
+			return result;
 	}
 
-	return result;
+	if (have_error)
+		*have_error = true;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+				 errmsg("timestamp out of range")));
+
+	return 0;
+}
+
+/*
+ * Single-argument version of timestamp2timestamptz_opt_error().
+ */
+static TimestampTz
+timestamp2timestamptz(Timestamp timestamp)
+{
+	return timestamp2timestamptz_opt_error(timestamp, NULL);
 }
 
 /* timestamptz_timestamp()
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index bd15bfa5bb0..c29f13aaf04 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -70,6 +70,8 @@ typedef struct
 /* date.c */
 extern int32 anytime_typmod_check(bool istz, int32 typmod);
 extern double date2timestamp_no_overflow(DateADT dateVal);
+extern Timestamp date2timestamp_opt_error(DateADT dateVal, bool *have_error);
+extern TimestampTz date2timestamptz_opt_error(DateADT dateVal, bool *have_error);
 extern void EncodeSpecialDate(DateADT dt, char *str);
 extern DateADT GetSQLCurrentDate(void);
 extern TimeTzADT *GetSQLCurrentTime(int32 typmod);
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index 0cafdd26538..5ebf336071b 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -337,5 +337,7 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
 extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
 
 extern void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
+extern bool AdjustTimestampForTypmodError(Timestamp *time, int32 typmod,
+										  bool *error);
 
 #endif							/* DATETIME_H */
diff --git a/src/include/utils/formatting.h b/src/include/utils/formatting.h
index beeaf10c332..165ca78ba5c 100644
--- a/src/include/utils/formatting.h
+++ b/src/include/utils/formatting.h
@@ -27,6 +27,7 @@ extern char *asc_toupper(const char *buff, size_t nbytes);
 extern char *asc_initcap(const char *buff, size_t nbytes);
 
 extern Datum parse_datetime(text *date_txt, text *fmt, bool std,
-							Oid *typid, int32 *typmod, int *tz);
+							Oid *typid, int32 *typmod, int *tz,
+							bool *have_error);
 
 #endif
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index ea16190ec3d..e884d4405f5 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -97,6 +97,9 @@ extern int	timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
 /* timestamp comparison works for timestamptz also */
 #define timestamptz_cmp_internal(dt1,dt2)	timestamp_cmp_internal(dt1, dt2)
 
+extern TimestampTz timestamp2timestamptz_opt_error(Timestamp timestamp,
+												   bool *have_error);
+
 extern int	isoweek2j(int year, int week);
 extern void isoweek2date(int woy, int *year, int *mon, int *mday);
 extern void isoweekdate2date(int isoweek, int wday, int *year, int *mon, int *mday);
0007-datetime-in-JsonbValue-5.patchapplication/x-patch; name=0007-datetime-in-JsonbValue-5.patchDownload
commit e365f481e2313c1bb8b37632e48eea6e90764fb8
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Sat Jul 20 21:50:48 2019 +0300

    Allow datetime values in JsonbValue
    
    SQL/JSON standard allows manipulation with datetime values.  So, it appears to
    be convinient to allow datetime values to be represented in JsonbValue struct.
    These datetime values are allowed for temporary representation only.  During
    serialization datetime values are converted into strings.
    
    SQL/JSON requires writing timestamps with timezone in the same timezone offset
    as they were parsed.  This is why we allow storage of timezone offset in
    JsonbValue struct.  For the same reason timezone offset argument is added to
    JsonEncodeDateTime() function.
    
    Extracted from original patch by Nikita Glukhov, Teodor Sigaev, Oleg Bartunov.
    Revised by me.  Comments were adjusted by Liudmila Mantrova.
    
    Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
    Discussion: https://postgr.es/m/CAPpHfdsZgYEra_PeCLGNoXOWYx6iU-S3wF8aX0ObQUcZU%2B4XTw%40mail.gmail.com
    Author: Nikita Glukhov, Teodor Sigaev, Oleg Bartunov, Alexander Korotkov, Liudmila Mantrova
    Reviewed-by: Anastasia Lubennikova

diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index 26d293709aa..d4ba3bd87db 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -1506,7 +1506,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 			{
 				char		buf[MAXDATELEN + 1];
 
-				JsonEncodeDateTime(buf, val, DATEOID);
+				JsonEncodeDateTime(buf, val, DATEOID, NULL);
 				appendStringInfo(result, "\"%s\"", buf);
 			}
 			break;
@@ -1514,7 +1514,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 			{
 				char		buf[MAXDATELEN + 1];
 
-				JsonEncodeDateTime(buf, val, TIMESTAMPOID);
+				JsonEncodeDateTime(buf, val, TIMESTAMPOID, NULL);
 				appendStringInfo(result, "\"%s\"", buf);
 			}
 			break;
@@ -1522,7 +1522,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 			{
 				char		buf[MAXDATELEN + 1];
 
-				JsonEncodeDateTime(buf, val, TIMESTAMPTZOID);
+				JsonEncodeDateTime(buf, val, TIMESTAMPTZOID, NULL);
 				appendStringInfo(result, "\"%s\"", buf);
 			}
 			break;
@@ -1550,10 +1550,11 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 
 /*
  * Encode 'value' of datetime type 'typid' into JSON string in ISO format using
- * optionally preallocated buffer 'buf'.
+ * optionally preallocated buffer 'buf'.  Optional 'tzp' determines time-zone
+ * offset (in seconds) in which we want to show timestamptz.
  */
 char *
-JsonEncodeDateTime(char *buf, Datum value, Oid typid)
+JsonEncodeDateTime(char *buf, Datum value, Oid typid, const int *tzp)
 {
 	if (!buf)
 		buf = palloc(MAXDATELEN + 1);
@@ -1630,11 +1631,30 @@ JsonEncodeDateTime(char *buf, Datum value, Oid typid)
 				const char *tzn = NULL;
 
 				timestamp = DatumGetTimestampTz(value);
+
+				/*
+				 * If a time zone is specified, we apply the time-zone shift,
+				 * convert timestamptz to pg_tm as if it were without a time
+				 * zone, and then use the specified time zone for converting
+				 * the timestamp into a string.
+				 */
+				if (tzp)
+				{
+					tz = *tzp;
+					timestamp -= (TimestampTz) tz * USECS_PER_SEC;
+				}
+
 				/* Same as timestamptz_out(), but forcing DateStyle */
 				if (TIMESTAMP_NOT_FINITE(timestamp))
 					EncodeSpecialTimestamp(timestamp, buf);
-				else if (timestamp2tm(timestamp, &tz, &tm, &fsec, &tzn, NULL) == 0)
+				else if (timestamp2tm(timestamp, tzp ? NULL : &tz, &tm, &fsec,
+									  tzp ? NULL : &tzn, NULL) == 0)
+				{
+					if (tzp)
+						tm.tm_isdst = 1;	/* set time-zone presence flag */
+
 					EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf);
+				}
 				else
 					ereport(ERROR,
 							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 69f41ab4556..74b4bbe44c6 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -206,6 +206,24 @@ JsonbTypeName(JsonbValue *jbv)
 			return "boolean";
 		case jbvNull:
 			return "null";
+		case jbvDatetime:
+			switch (jbv->val.datetime.typid)
+			{
+				case DATEOID:
+					return "date";
+				case TIMEOID:
+					return "time without time zone";
+				case TIMETZOID:
+					return "time with time zone";
+				case TIMESTAMPOID:
+					return "timestamp without time zone";
+				case TIMESTAMPTZOID:
+					return "timestamp with time zone";
+				default:
+					elog(ERROR, "unrecognized jsonb value datetime type: %d",
+						 jbv->val.datetime.typid);
+			}
+			return "unknown";
 		default:
 			elog(ERROR, "unrecognized jsonb value type: %d", jbv->type);
 			return "unknown";
@@ -805,17 +823,20 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result,
 				break;
 			case JSONBTYPE_DATE:
 				jb.type = jbvString;
-				jb.val.string.val = JsonEncodeDateTime(NULL, val, DATEOID);
+				jb.val.string.val = JsonEncodeDateTime(NULL, val,
+													   DATEOID, NULL);
 				jb.val.string.len = strlen(jb.val.string.val);
 				break;
 			case JSONBTYPE_TIMESTAMP:
 				jb.type = jbvString;
-				jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPOID);
+				jb.val.string.val = JsonEncodeDateTime(NULL, val,
+													   TIMESTAMPOID, NULL);
 				jb.val.string.len = strlen(jb.val.string.val);
 				break;
 			case JSONBTYPE_TIMESTAMPTZ:
 				jb.type = jbvString;
-				jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPTZOID);
+				jb.val.string.val = JsonEncodeDateTime(NULL, val,
+													   TIMESTAMPTZOID, NULL);
 				jb.val.string.len = strlen(jb.val.string.val);
 				break;
 			case JSONBTYPE_JSONCAST:
diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index ac04c4a57bc..dee6e1bef8c 100644
--- a/src/backend/utils/adt/jsonb_util.c
+++ b/src/backend/utils/adt/jsonb_util.c
@@ -14,9 +14,12 @@
 #include "postgres.h"
 
 #include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
 #include "miscadmin.h"
 #include "utils/builtins.h"
+#include "utils/datetime.h"
 #include "utils/hashutils.h"
+#include "utils/jsonapi.h"
 #include "utils/jsonb.h"
 #include "utils/memutils.h"
 #include "utils/varlena.h"
@@ -242,6 +245,8 @@ compareJsonbContainers(JsonbContainer *a, JsonbContainer *b)
 						break;
 					case jbvBinary:
 						elog(ERROR, "unexpected jbvBinary value");
+					case jbvDatetime:
+						elog(ERROR, "unexpected jbvDatetime value");
 				}
 			}
 			else
@@ -1749,6 +1754,22 @@ convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal)
 				JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE;
 			break;
 
+		case jbvDatetime:
+			{
+				char		buf[MAXDATELEN + 1];
+				size_t		len;
+
+				JsonEncodeDateTime(buf,
+								   scalarVal->val.datetime.value,
+								   scalarVal->val.datetime.typid,
+								   &scalarVal->val.datetime.tz);
+				len = strlen(buf);
+				appendToBuffer(buffer, buf, len);
+
+				*jentry = len;
+			}
+			break;
+
 		default:
 			elog(ERROR, "invalid jsonb scalar type");
 	}
diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h
index 1c56acca55a..e1dab24d5dd 100644
--- a/src/include/utils/jsonapi.h
+++ b/src/include/utils/jsonapi.h
@@ -161,6 +161,7 @@ extern Jsonb *transform_jsonb_string_values(Jsonb *jsonb, void *action_state,
 extern text *transform_json_string_values(text *json, void *action_state,
 										  JsonTransformStringValuesAction transform_action);
 
-extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid);
+extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid,
+								const int *tzp);
 
 #endif							/* JSONAPI_H */
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index ac52b75f51d..80ef327cc09 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -241,7 +241,15 @@ enum jbvType
 	jbvArray = 0x10,
 	jbvObject,
 	/* Binary (i.e. struct Jsonb) jbvArray/jbvObject */
-	jbvBinary
+	jbvBinary,
+
+	/*
+	 * Virtual types.
+	 *
+	 * These types are used only for in-memory JSON processing and serialized
+	 * into JSON strings when outputted to json/jsonb.
+	 */
+	jbvDatetime = 0x20,
 };
 
 /*
@@ -282,11 +290,21 @@ struct JsonbValue
 			int			len;
 			JsonbContainer *data;
 		}			binary;		/* Array or object, in on-disk format */
+
+		struct
+		{
+			Datum		value;
+			Oid			typid;
+			int32		typmod;
+			int			tz;		/* Numeric time zone, in seconds, for
+								 * TimestampTz data type */
+		}			datetime;
 	}			val;
 };
 
-#define IsAJsonbScalar(jsonbval)	((jsonbval)->type >= jbvNull && \
-									 (jsonbval)->type <= jbvBool)
+#define IsAJsonbScalar(jsonbval)	(((jsonbval)->type >= jbvNull && \
+									  (jsonbval)->type <= jbvBool) || \
+									  (jsonbval)->type == jbvDatetime)
 
 /*
  * Key/value pair within an Object.
0008-implement-jsonpath-datetime-5.patchapplication/x-patch; name=0008-implement-jsonpath-datetime-5.patchDownload
commit 70954d61182f472c6d81bd98413fcfd78d78b8c7
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Sun Jul 21 01:16:38 2019 +0300

    Implement jsonpath .datetime() method
    
    This commit implements jsonpath .datetime() method as it's specified in
    SQL/JSON standard.  There are no-argument and single-argument versions of
    this method.  No-argument version selects first of ISO datetime formats
    matching input string.  Single-argument version accepts template string as
    its argument.
    
    Additionally to .datetime() method itself this commit also implements
    comparison ability of resulting date and time values.  There is some difficulty
    because exising jsonb_path_*() functions are immutable, while comparison of
    timezoned and non-timezoned types involves current timezone.  At first, current
    timezone could be changes in session.  Moreover, timezones themselves are not
    immutable and could be updated.  This is why we let existing immutable functions
    throw errors on such non-immutable comparison.  In the same time this commit
    provides jsonb_path_*_tz() functions which are stable and support operations
    involving timezones.
    
    Support of .datetime() method was the only blocker prevents T832 from being
    marked as supported.  sql_features.txt is updated correspondingly.
    
    Extracted from original patch by Nikita Glukhov, Teodor Sigaev, Oleg Bartunov.
    Heavily revised by me.  Comments were adjusted by Liudmila Mantrova.
    
    Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
    Discussion: https://postgr.es/m/CAPpHfdsZgYEra_PeCLGNoXOWYx6iU-S3wF8aX0ObQUcZU%2B4XTw%40mail.gmail.com
    Author: Alexander Korotkov, Nikita Glukhov, Teodor Sigaev, Oleg Bartunov, Liudmila Mantrova
    Reviewed-by: Anastasia Lubennikova

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index c41aeeaa3b7..8665d0349e3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -11744,16 +11744,6 @@ table2-mapping
   </para>
 
   <itemizedlist>
-   <listitem>
-    <para>
-     <literal>.datetime()</literal> item method is not implemented yet
-     mainly because immutable <type>jsonpath</type> functions and operators
-     cannot reference session timezone, which is used in some datetime
-     operations.  Datetime support will be added to <type>jsonpath</type>
-     in future versions of <productname>PostgreSQL</productname>.
-    </para>
-   </listitem>
-
    <listitem>
     <para>
      A path expression can be a Boolean predicate, although the SQL/JSON
@@ -11959,6 +11949,20 @@ table2-mapping
         <entry><literal>$.z.abs()</literal></entry>
         <entry><literal>0.3</literal></entry>
        </row>
+       <row>
+        <entry><literal>datetime()</literal></entry>
+        <entry>Date/time value converted from a string</entry>
+        <entry><literal>["2015-8-1", "2015-08-12"]</literal></entry>
+        <entry><literal>$[*] ? (@.datetime() &lt; "2015-08-2". datetime())</literal></entry>
+        <entry><literal>2015-8-1</literal></entry>
+       </row>
+       <row>
+        <entry><literal>datetime(<replaceable>template</replaceable>)</literal></entry>
+        <entry>Date/time value converted from a string using the specified template</entry>
+        <entry><literal>["12:30", "18:40"]</literal></entry>
+        <entry><literal>$[*].datetime("HH24:MI")</literal></entry>
+        <entry><literal>"12:30:00", "18:40:00"</literal></entry>
+       </row>
        <row>
         <entry><literal>keyvalue()</literal></entry>
         <entry>
@@ -11976,6 +11980,26 @@ table2-mapping
      </tgroup>
     </table>
 
+    <note>
+     <para>
+      The result type of <literal>datetime()</literal> and
+      <literal>datetime(<replaceable>template</replaceable>)</literal>
+      methods can be <type>date</type>, <type>timetz</type>, <type>time</type>,
+      <type>timestamptz</type>, or <type>timestamp</type>.
+      Both methods determine the result type dynamically.
+     </para>
+     <para>
+      The <literal>datetime()</literal> method sequentially tries ISO formats
+      for <type>date</type>, <type>timetz</type>, <type>time</type>,
+      <type>timestamptz</type>, and <type>timestamp</type>. It stops on
+      the first matching format and the corresponding data type.
+     </para>
+     <para>
+      The <literal>datetime(<replaceable>template</replaceable>)</literal>
+      method determines the result type by the provided template string.
+     </para>
+    </note>
+
     <table id="functions-sqljson-filter-ex-table">
      <title><type>jsonpath</type> Filter Expression Elements</title>
      <tgroup cols="5">
@@ -12118,6 +12142,15 @@ table2-mapping
       </tbody>
      </tgroup>
     </table>
+
+    <note>
+     <para>
+      When different date/time values are compared, an implicit cast is
+      applied. A <type>date</type> value can be cast to <type>timestamp</type>
+      or <type>timestamptz</type>, <type>timestamp</type> can be cast to
+      <type>timestamptz</type>, and <type>time</type> &mdash; to <type>timetz</type>.
+     </para>
+    </note>
    </sect3>
 
   </sect2>
@@ -12351,7 +12384,7 @@ table2-mapping
    <para>
     The <literal>@?</literal> and <literal>@@</literal> operators suppress
     the following errors: lacking object field or array element, unexpected
-    JSON item type, and numeric errors.
+    JSON item type, datetime and numeric errors.
     This behavior might be helpful while searching over JSON document
     collections of varying structure.
    </para>
@@ -12620,18 +12653,33 @@ table2-mapping
   <indexterm>
    <primary>jsonb_path_exists</primary>
   </indexterm>
+  <indexterm>
+   <primary>jsonb_path_exists_tz</primary>
+  </indexterm>
   <indexterm>
    <primary>jsonb_path_match</primary>
   </indexterm>
+  <indexterm>
+   <primary>jsonb_path_match_tz</primary>
+  </indexterm>
   <indexterm>
    <primary>jsonb_path_query</primary>
   </indexterm>
+  <indexterm>
+   <primary>jsonb_path_query_tz</primary>
+  </indexterm>
   <indexterm>
    <primary>jsonb_path_query_array</primary>
   </indexterm>
+  <indexterm>
+   <primary>jsonb_path_query_array_tz</primary>
+  </indexterm>
   <indexterm>
    <primary>jsonb_path_query_first</primary>
   </indexterm>
+  <indexterm>
+   <primary>jsonb_path_query_first_tz</primary>
+  </indexterm>
 
   <table id="functions-json-processing-table">
     <title>JSON Processing Functions</title>
@@ -12971,6 +13019,9 @@ table2-mapping
          <para><literal>
            jsonb_path_exists(target jsonb, path jsonpath [, vars jsonb, silent bool])
          </literal></para>
+         <para><literal>
+           jsonb_path_exists_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>boolean</type></entry>
         <entry>
@@ -12991,6 +13042,9 @@ table2-mapping
          <para><literal>
            jsonb_path_match(target jsonb, path jsonpath [, vars jsonb, silent bool])
          </literal></para>
+         <para><literal>
+           jsonb_path_match_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>boolean</type></entry>
         <entry>
@@ -13013,6 +13067,9 @@ table2-mapping
          <para><literal>
            jsonb_path_query(target jsonb, path jsonpath [, vars jsonb, silent bool])
          </literal></para>
+         <para><literal>
+           jsonb_path_query_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>setof jsonb</type></entry>
         <entry>
@@ -13041,6 +13098,9 @@ table2-mapping
          <para><literal>
            jsonb_path_query_array(target jsonb, path jsonpath [, vars jsonb, silent bool])
          </literal></para>
+         <para><literal>
+           jsonb_path_query_array_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>jsonb</type></entry>
         <entry>
@@ -13061,6 +13121,9 @@ table2-mapping
          <para><literal>
            jsonb_path_query_first(target jsonb, path jsonpath [, vars jsonb, silent bool])
          </literal></para>
+         <para><literal>
+           jsonb_path_query_first_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>jsonb</type></entry>
         <entry>
@@ -13203,11 +13266,8 @@ table2-mapping
 
   <note>
    <para>
-    The <literal>jsonb_path_exists</literal>, <literal>jsonb_path_match</literal>,
-    <literal>jsonb_path_query</literal>, <literal>jsonb_path_query_array</literal> and
-    <literal>jsonb_path_query_first</literal>
-    functions have optional <literal>vars</literal> and <literal>silent</literal>
-    arguments.
+    The <literal>jsonb_path_*</literal> functions have optional
+    <literal>vars</literal> and <literal>silent</literal> arguments.
    </para>
    <para>
     If the <literal>vars</literal> argument is specified, it provides an
@@ -13221,6 +13281,20 @@ table2-mapping
    </para>
   </note>
 
+  <note>
+   <para>
+    Some of the <literal>jsonb_path_*</literal> functions have the
+    <literal>_tz</literal> suffix. These functions have been implemented to
+    support comparison of date/time values that involves implicit
+    timezone-aware casts. Since operations with time zones are not immutable,
+    these functions are qualified as stable. Their counterparts without the
+    suffix do not support such casts, so they are immutable and can be used for
+    such use-cases as expression indexes
+    (see <xref linkend="indexes-expressional"/>). There is no difference
+    between these functions for other <type>jsonpath</type> operations.
+   </para>
+  </note>
+
   <para>
     See also <xref linkend="functions-aggregate"/> for the aggregate
     function <function>json_agg</function> which aggregates record
diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index 059ec02cd03..9c68292a54d 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -544,7 +544,7 @@ T828	JSON_QUERY			NO
 T829	JSON_QUERY: array wrapper options			NO	
 T830	Enforcing unique keys in SQL/JSON constructor functions			NO	
 T831	SQL/JSON path language: strict mode			YES	
-T832	SQL/JSON path language: item method			NO	datetime() not yet implemented
+T832	SQL/JSON path language: item method			YES	
 T833	SQL/JSON path language: multiple subscripts			YES	
 T834	SQL/JSON path language: wildcard member accessor			YES	
 T835	SQL/JSON path language: filter expressions			YES	
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ea4c85e3959..423ae4fdc8a 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1287,6 +1287,46 @@ LANGUAGE INTERNAL
 STRICT IMMUTABLE PARALLEL SAFE
 AS 'jsonb_path_query_first';
 
+CREATE OR REPLACE FUNCTION
+  jsonb_path_exists_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                    silent boolean DEFAULT false)
+RETURNS boolean
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_exists_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_match_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                   silent boolean DEFAULT false)
+RETURNS boolean
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_match_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_query_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                   silent boolean DEFAULT false)
+RETURNS SETOF jsonb
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_query_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_query_array_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                         silent boolean DEFAULT false)
+RETURNS jsonb
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_query_array_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_query_first_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                         silent boolean DEFAULT false)
+RETURNS jsonb
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_query_first_tz';
+
 --
 -- The default permissions for functions mean that anyone can execute them.
 -- A number of functions shouldn't be executable by just anyone, but rather
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 87ae60e490f..65c047acc86 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -337,12 +337,14 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
 		case jpiPlus:
 		case jpiMinus:
 		case jpiExists:
+		case jpiDatetime:
 			{
 				int32		arg = reserveSpaceForItemPointer(buf);
 
-				chld = flattenJsonPathParseItem(buf, item->value.arg,
-												nestingLevel + argNestingLevel,
-												insideArraySubscript);
+				chld = !item->value.arg ? pos :
+					flattenJsonPathParseItem(buf, item->value.arg,
+											 nestingLevel + argNestingLevel,
+											 insideArraySubscript);
 				*(int32 *) (buf->data + arg) = chld - pos;
 			}
 			break;
@@ -692,6 +694,15 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
 		case jpiDouble:
 			appendBinaryStringInfo(buf, ".double()", 9);
 			break;
+		case jpiDatetime:
+			appendBinaryStringInfo(buf, ".datetime(", 10);
+			if (v->content.arg)
+			{
+				jspGetArg(v, &elem);
+				printJsonPathItem(buf, &elem, false, false);
+			}
+			appendStringInfoChar(buf, ')');
+			break;
 		case jpiKeyValue:
 			appendBinaryStringInfo(buf, ".keyvalue()", 11);
 			break;
@@ -754,6 +765,8 @@ jspOperationName(JsonPathItemType type)
 			return "floor";
 		case jpiCeiling:
 			return "ceiling";
+		case jpiDatetime:
+			return "datetime";
 		default:
 			elog(ERROR, "unrecognized jsonpath item type: %d", type);
 			return NULL;
@@ -889,6 +902,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
 		case jpiPlus:
 		case jpiMinus:
 		case jpiFilter:
+		case jpiDatetime:
 			read_int32(v->content.arg, base, pos);
 			break;
 		case jpiIndexArray:
@@ -913,7 +927,8 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a)
 		   v->type == jpiIsUnknown ||
 		   v->type == jpiExists ||
 		   v->type == jpiPlus ||
-		   v->type == jpiMinus);
+		   v->type == jpiMinus ||
+		   v->type == jpiDatetime);
 
 	jspInitByBuffer(a, v->base, v->content.arg);
 }
@@ -961,6 +976,7 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
 			   v->type == jpiFloor ||
 			   v->type == jpiCeiling ||
 			   v->type == jpiDouble ||
+			   v->type == jpiDatetime ||
 			   v->type == jpiKeyValue ||
 			   v->type == jpiStartsWith);
 
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index 21106e1da86..9dcc91da5c0 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -66,6 +66,7 @@
 #include "miscadmin.h"
 #include "regex/regex.h"
 #include "utils/builtins.h"
+#include "utils/datetime.h"
 #include "utils/datum.h"
 #include "utils/formatting.h"
 #include "utils/float.h"
@@ -107,6 +108,7 @@ typedef struct JsonPathExecContext
 										 * ignored */
 	bool		throwErrors;	/* with "false" all suppressible errors are
 								 * suppressed */
+	bool		useTz;
 } JsonPathExecContext;
 
 /* Context for LIKE_REGEX execution. */
@@ -173,7 +175,8 @@ typedef JsonPathBool (*JsonPathPredicateCallback) (JsonPathItem *jsp,
 typedef Numeric (*BinaryArithmFunc) (Numeric num1, Numeric num2, bool *error);
 
 static JsonPathExecResult executeJsonPath(JsonPath *path, Jsonb *vars,
-										  Jsonb *json, bool throwErrors, JsonValueList *result);
+										  Jsonb *json, bool throwErrors,
+										  JsonValueList *result, bool useTz);
 static JsonPathExecResult executeItem(JsonPathExecContext *cxt,
 									  JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
 static JsonPathExecResult executeItemOptUnwrapTarget(JsonPathExecContext *cxt,
@@ -214,6 +217,8 @@ static JsonPathBool executeLikeRegex(JsonPathItem *jsp, JsonbValue *str,
 static JsonPathExecResult executeNumericItemMethod(JsonPathExecContext *cxt,
 												   JsonPathItem *jsp, JsonbValue *jb, bool unwrap, PGFunction func,
 												   JsonValueList *found);
+static JsonPathExecResult executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
+												JsonbValue *jb, JsonValueList *found);
 static JsonPathExecResult executeKeyValueMethod(JsonPathExecContext *cxt,
 												JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
 static JsonPathExecResult appendBoolResult(JsonPathExecContext *cxt,
@@ -225,7 +230,8 @@ static void getJsonPathVariable(JsonPathExecContext *cxt,
 static int	JsonbArraySize(JsonbValue *jb);
 static JsonPathBool executeComparison(JsonPathItem *cmp, JsonbValue *lv,
 									  JsonbValue *rv, void *p);
-static JsonPathBool compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2);
+static JsonPathBool compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2,
+								 bool useTz);
 static int	compareNumeric(Numeric a, Numeric b);
 static JsonbValue *copyJsonbValue(JsonbValue *src);
 static JsonPathExecResult getArrayIndex(JsonPathExecContext *cxt,
@@ -246,6 +252,8 @@ static JsonbValue *JsonbInitBinary(JsonbValue *jbv, Jsonb *jb);
 static int	JsonbType(JsonbValue *jb);
 static JsonbValue *getScalar(JsonbValue *scalar, enum jbvType type);
 static JsonbValue *wrapItemsInArray(const JsonValueList *items);
+static int	compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
+							bool useTz, bool *have_error);
 
 /****************** User interface to JsonPath executor ********************/
 
@@ -261,8 +269,8 @@ static JsonbValue *wrapItemsInArray(const JsonValueList *items);
  *		SQL/JSON.  Regarding jsonb_path_match(), this function doesn't have
  *		an analogy in SQL/JSON, so we define its behavior on our own.
  */
-Datum
-jsonb_path_exists(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_exists_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -276,7 +284,7 @@ jsonb_path_exists(PG_FUNCTION_ARGS)
 		silent = PG_GETARG_BOOL(3);
 	}
 
-	res = executeJsonPath(jp, vars, jb, !silent, NULL);
+	res = executeJsonPath(jp, vars, jb, !silent, NULL, tz);
 
 	PG_FREE_IF_COPY(jb, 0);
 	PG_FREE_IF_COPY(jp, 1);
@@ -287,6 +295,18 @@ jsonb_path_exists(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(res == jperOk);
 }
 
+Datum
+jsonb_path_exists(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_exists_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_exists_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_exists_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_exists_opr
  *		Implementation of operator "jsonb @? jsonpath" (2-argument version of
@@ -296,7 +316,7 @@ Datum
 jsonb_path_exists_opr(PG_FUNCTION_ARGS)
 {
 	/* just call the other one -- it can handle both cases */
-	return jsonb_path_exists(fcinfo);
+	return jsonb_path_exists_internal(fcinfo, false);
 }
 
 /*
@@ -304,8 +324,8 @@ jsonb_path_exists_opr(PG_FUNCTION_ARGS)
  *		Returns jsonpath predicate result item for the specified jsonb value.
  *		See jsonb_path_exists() comment for details regarding error handling.
  */
-Datum
-jsonb_path_match(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_match_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -319,7 +339,7 @@ jsonb_path_match(PG_FUNCTION_ARGS)
 		silent = PG_GETARG_BOOL(3);
 	}
 
-	(void) executeJsonPath(jp, vars, jb, !silent, &found);
+	(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 	PG_FREE_IF_COPY(jb, 0);
 	PG_FREE_IF_COPY(jp, 1);
@@ -343,6 +363,18 @@ jsonb_path_match(PG_FUNCTION_ARGS)
 	PG_RETURN_NULL();
 }
 
+Datum
+jsonb_path_match(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_match_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_match_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_match_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_match_opr
  *		Implementation of operator "jsonb @@ jsonpath" (2-argument version of
@@ -352,7 +384,7 @@ Datum
 jsonb_path_match_opr(PG_FUNCTION_ARGS)
 {
 	/* just call the other one -- it can handle both cases */
-	return jsonb_path_match(fcinfo);
+	return jsonb_path_match_internal(fcinfo, false);
 }
 
 /*
@@ -360,8 +392,8 @@ jsonb_path_match_opr(PG_FUNCTION_ARGS)
  *		Executes jsonpath for given jsonb document and returns result as
  *		rowset.
  */
-Datum
-jsonb_path_query(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_query_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	FuncCallContext *funcctx;
 	List	   *found;
@@ -385,7 +417,7 @@ jsonb_path_query(PG_FUNCTION_ARGS)
 		vars = PG_GETARG_JSONB_P_COPY(2);
 		silent = PG_GETARG_BOOL(3);
 
-		(void) executeJsonPath(jp, vars, jb, !silent, &found);
+		(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 		funcctx->user_fctx = JsonValueListGetList(&found);
 
@@ -406,13 +438,25 @@ jsonb_path_query(PG_FUNCTION_ARGS)
 	SRF_RETURN_NEXT(funcctx, JsonbPGetDatum(JsonbValueToJsonb(v)));
 }
 
+Datum
+jsonb_path_query(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_query_array
  *		Executes jsonpath for given jsonb document and returns result as
  *		jsonb array.
  */
-Datum
-jsonb_path_query_array(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_query_array_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -420,18 +464,30 @@ jsonb_path_query_array(PG_FUNCTION_ARGS)
 	Jsonb	   *vars = PG_GETARG_JSONB_P(2);
 	bool		silent = PG_GETARG_BOOL(3);
 
-	(void) executeJsonPath(jp, vars, jb, !silent, &found);
+	(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 	PG_RETURN_JSONB_P(JsonbValueToJsonb(wrapItemsInArray(&found)));
 }
 
+Datum
+jsonb_path_query_array(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_array_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_array_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_array_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_query_first
  *		Executes jsonpath for given jsonb document and returns first result
  *		item.  If there are no items, NULL returned.
  */
-Datum
-jsonb_path_query_first(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_query_first_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -439,7 +495,7 @@ jsonb_path_query_first(PG_FUNCTION_ARGS)
 	Jsonb	   *vars = PG_GETARG_JSONB_P(2);
 	bool		silent = PG_GETARG_BOOL(3);
 
-	(void) executeJsonPath(jp, vars, jb, !silent, &found);
+	(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 	if (JsonValueListLength(&found) >= 1)
 		PG_RETURN_JSONB_P(JsonbValueToJsonb(JsonValueListHead(&found)));
@@ -447,6 +503,18 @@ jsonb_path_query_first(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 }
 
+Datum
+jsonb_path_query_first(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_first_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_first_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_first_internal(fcinfo, true);
+}
+
 /********************Execute functions for JsonPath**************************/
 
 /*
@@ -470,7 +538,7 @@ jsonb_path_query_first(PG_FUNCTION_ARGS)
  */
 static JsonPathExecResult
 executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors,
-				JsonValueList *result)
+				JsonValueList *result, bool useTz)
 {
 	JsonPathExecContext cxt;
 	JsonPathExecResult res;
@@ -500,6 +568,7 @@ executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors,
 	cxt.lastGeneratedObjectId = vars ? 2 : 1;
 	cxt.innermostArraySize = -1;
 	cxt.throwErrors = throwErrors;
+	cxt.useTz = useTz;
 
 	if (jspStrictAbsenseOfErrors(&cxt) && !result)
 	{
@@ -1028,6 +1097,12 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
 			}
 			break;
 
+		case jpiDatetime:
+			if (unwrap && JsonbType(jb) == jbvArray)
+				return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
+
+			return executeDateTimeMethod(cxt, jsp, jb, found);
+
 		case jpiKeyValue:
 			if (unwrap && JsonbType(jb) == jbvArray)
 				return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
@@ -1214,7 +1289,7 @@ executeBoolItem(JsonPathExecContext *cxt, JsonPathItem *jsp,
 			jspGetLeftArg(jsp, &larg);
 			jspGetRightArg(jsp, &rarg);
 			return executePredicate(cxt, jsp, &larg, &rarg, jb, true,
-									executeComparison, NULL);
+									executeComparison, cxt);
 
 		case jpiStartsWith:		/* 'whole STARTS WITH initial' */
 			jspGetLeftArg(jsp, &larg);	/* 'whole' */
@@ -1717,6 +1792,138 @@ executeNumericItemMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
 	return executeNextItem(cxt, jsp, &next, jb, found, false);
 }
 
+/*
+ * Implementation of the .datetime() method.
+ *
+ * Converts a string into a date/time value. The actual type is determined at run time.
+ * If an argument is provided, this argument is used as a template string.
+ * Otherwise, the first fitting ISO format is selected.
+ */
+static JsonPathExecResult
+executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
+					  JsonbValue *jb, JsonValueList *found)
+{
+	JsonbValue	jbvbuf;
+	Datum		value;
+	text	   *datetime;
+	Oid			typid;
+	int32		typmod = -1;
+	int			tz = 0;
+	bool		hasNext;
+	JsonPathExecResult res = jperNotFound;
+	JsonPathItem elem;
+
+	if (!(jb = getScalar(jb, jbvString)))
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+							  errmsg("invalid argument for SQL/JSON datetime function"),
+							  errdetail("jsonpath item method .%s() can only be applied to a string",
+										jspOperationName(jsp->type)))));
+
+	datetime = cstring_to_text_with_len(jb->val.string.val,
+										jb->val.string.len);
+
+	if (jsp->content.arg)
+	{
+		text	   *template;
+		char	   *template_str;
+		int			template_len;
+		bool		have_error = false;
+
+		jspGetArg(jsp, &elem);
+
+		if (elem.type != jpiString)
+			elog(ERROR, "invalid jsonpath item type for .datetime() argument");
+
+		template_str = jspGetString(&elem, &template_len);
+
+		template = cstring_to_text_with_len(template_str,
+											template_len);
+
+		value = parse_datetime(datetime, template, true,
+							   &typid, &typmod, &tz,
+							   jspThrowErrors(cxt) ? NULL : &have_error);
+
+		if (have_error)
+			res = jperError;
+		else
+			res = jperOk;
+	}
+	else
+	{
+		/*
+		 * According to SQL/JSON standard enumerate ISO formats for: date,
+		 * timetz, time, timestamptz, timestamp.
+		 */
+		static const char *fmt_str[] =
+		{
+			"yyyy-mm-dd",
+			"HH24:MI:SS TZH:TZM",
+			"HH24:MI:SS TZH",
+			"HH24:MI:SS",
+			"yyyy-mm-dd HH24:MI:SS TZH:TZM",
+			"yyyy-mm-dd HH24:MI:SS TZH",
+			"yyyy-mm-dd HH24:MI:SS"
+		};
+
+		/* cache for format texts */
+		static text *fmt_txt[lengthof(fmt_str)] = {0};
+		int			i;
+
+		/* loop until datetime format fits */
+		for (i = 0; i < lengthof(fmt_str); i++)
+		{
+			bool		have_error = false;
+
+			if (!fmt_txt[i])
+			{
+				MemoryContext oldcxt =
+				MemoryContextSwitchTo(TopMemoryContext);
+
+				fmt_txt[i] = cstring_to_text(fmt_str[i]);
+				MemoryContextSwitchTo(oldcxt);
+			}
+
+			value = parse_datetime(datetime, fmt_txt[i], true,
+								   &typid, &typmod, &tz,
+								   &have_error);
+
+			if (!have_error)
+			{
+				res = jperOk;
+				break;
+			}
+		}
+
+		if (res == jperNotFound)
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+								  errmsg("invalid argument for SQL/JSON datetime function"),
+								  errdetail("unrecognized datetime format"),
+								  errhint("use datetime template argument for explicit format specification"))));
+	}
+
+	pfree(datetime);
+
+	if (jperIsError(res))
+		return res;
+
+	hasNext = jspGetNext(jsp, &elem);
+
+	if (!hasNext && !found)
+		return res;
+
+	jb = hasNext ? &jbvbuf : palloc(sizeof(*jb));
+
+	jb->type = jbvDatetime;
+	jb->val.datetime.value = value;
+	jb->val.datetime.typid = typid;
+	jb->val.datetime.typmod = typmod;
+	jb->val.datetime.tz = tz;
+
+	return executeNextItem(cxt, jsp, &elem, jb, found, hasNext);
+}
+
 /*
  * Implementation of .keyvalue() method.
  *
@@ -1977,7 +2184,9 @@ JsonbArraySize(JsonbValue *jb)
 static JsonPathBool
 executeComparison(JsonPathItem *cmp, JsonbValue *lv, JsonbValue *rv, void *p)
 {
-	return compareItems(cmp->type, lv, rv);
+	JsonPathExecContext *cxt = (JsonPathExecContext *) p;
+
+	return compareItems(cmp->type, lv, rv, cxt->useTz);
 }
 
 /*
@@ -2072,7 +2281,7 @@ compareStrings(const char *mbstr1, int mblen1,
  * Compare two SQL/JSON items using comparison operation 'op'.
  */
 static JsonPathBool
-compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2)
+compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2, bool useTz)
 {
 	int			cmp;
 	bool		res;
@@ -2113,6 +2322,21 @@ compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2)
 			cmp = compareStrings(jb1->val.string.val, jb1->val.string.len,
 								 jb2->val.string.val, jb2->val.string.len);
 			break;
+		case jbvDatetime:
+			{
+				bool		have_error = false;
+
+				cmp = compareDatetime(jb1->val.datetime.value,
+									  jb1->val.datetime.typid,
+									  jb2->val.datetime.value,
+									  jb2->val.datetime.typid,
+									  useTz,
+									  &have_error);
+
+				if (have_error)
+					return jpbUnknown;
+			}
+			break;
 
 		case jbvBinary:
 		case jbvArray:
@@ -2372,3 +2596,205 @@ wrapItemsInArray(const JsonValueList *items)
 
 	return pushJsonbValue(&ps, WJB_END_ARRAY, NULL);
 }
+
+/*
+ * Cross-type comparison of two datetime SQL/JSON items.  If items are
+ * uncomparable, 'error' flag is set.
+ */
+static int
+compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
+				bool useTz, bool *have_error)
+{
+	PGFunction cmpfunc = NULL;
+
+	switch (typid1)
+	{
+		case DATEOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					cmpfunc = date_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					val1 = date2timestamp_opt_error(val1, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"date", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val1 = date2timestamptz_opt_error(val1, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMEOID:
+			switch (typid2)
+			{
+				case TIMEOID:
+					cmpfunc = time_cmp;
+
+					break;
+
+				case TIMETZOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"time", "timetz"),
+								 errhint("use *_tz() function for timezone support")));
+					val1 = DirectFunctionCall1(time_timetz, val1);
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case DATEOID:
+				case TIMESTAMPOID:
+				case TIMESTAMPTZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMETZOID:
+			switch (typid2)
+			{
+				case TIMEOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"time", "timetz"),
+								 errhint("use *_tz() function for timezone support")));
+					val2 = DirectFunctionCall1(time_timetz, val2);
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case TIMETZOID:
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case DATEOID:
+				case TIMESTAMPOID:
+				case TIMESTAMPTZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMESTAMPOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					val2 = date2timestamp_opt_error(val2, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"timestamp", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val1 = timestamp2timestamptz_opt_error(val1, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMESTAMPTZOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"date", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val2 = date2timestamptz_opt_error(val2, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"timestamp", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val2 = timestamp2timestamptz_opt_error(val2, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		default:
+			elog(ERROR, "unrecognized SQL/JSON datetime type oid: %d",
+				 typid1);
+	}
+
+	if (*have_error)
+		return 0;
+
+	if (!cmpfunc)
+		elog(ERROR, "unrecognized SQL/JSON datetime type oid: %d",
+			 typid2);
+
+	*have_error = false;
+
+	return DatumGetInt32(DirectFunctionCall2(cmpfunc, val1, val2));
+}
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
index 91b4b2f5985..059faeeb2c7 100644
--- a/src/backend/utils/adt/jsonpath_gram.y
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -94,12 +94,14 @@ static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr,
 %token	<str>		LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
 %token	<str>		ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P
 %token	<str>		ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P KEYVALUE_P
+%token	<str>		DATETIME_P
 
 %type	<result>	result
 
 %type	<value>		scalar_value path_primary expr array_accessor
 					any_path accessor_op key predicate delimited_predicate
 					index_elem starts_with_initial expr_or_predicate
+					datetime_template opt_datetime_template
 
 %type	<elems>		accessor_expr
 
@@ -247,9 +249,20 @@ accessor_op:
 	| array_accessor				{ $$ = $1; }
 	| '.' any_path					{ $$ = $2; }
 	| '.' method '(' ')'			{ $$ = makeItemType($2); }
+	| '.' DATETIME_P '(' opt_datetime_template ')'
+									{ $$ = makeItemUnary(jpiDatetime, $4); }
 	| '?' '(' predicate ')'			{ $$ = makeItemUnary(jpiFilter, $3); }
 	;
 
+datetime_template:
+	STRING_P						{ $$ = makeItemString(&$1); }
+	;
+
+opt_datetime_template:
+	datetime_template				{ $$ = $1; }
+	| /* EMPTY */					{ $$ = NULL; }
+	;
+
 key:
 	key_name						{ $$ = makeItemKey(&$1); }
 	;
@@ -272,6 +285,7 @@ key_name:
 	| FLOOR_P
 	| DOUBLE_P
 	| CEILING_P
+	| DATETIME_P
 	| KEYVALUE_P
 	| LAST_P
 	| STARTS_P
diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l
index 2165ffcc254..ced65edb59b 100644
--- a/src/backend/utils/adt/jsonpath_scan.l
+++ b/src/backend/utils/adt/jsonpath_scan.l
@@ -340,6 +340,7 @@ static const JsonPathKeyword keywords[] = {
 	{ 6, false,	STRICT_P,	"strict"},
 	{ 7, false,	CEILING_P,	"ceiling"},
 	{ 7, false,	UNKNOWN_P,	"unknown"},
+	{ 8, false,	DATETIME_P,	"datetime"},
 	{ 8, false,	KEYVALUE_P,	"keyvalue"},
 	{ 10,false, LIKE_REGEX_P, "like_regex"},
 };
diff --git a/src/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index 867e98b690b..2bd283085e3 100644
--- a/src/backend/utils/errcodes.txt
+++ b/src/backend/utils/errcodes.txt
@@ -207,6 +207,7 @@ Section: Class 22 - Data Exception
 2200S    E    ERRCODE_INVALID_XML_COMMENT                                    invalid_xml_comment
 2200T    E    ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION                     invalid_xml_processing_instruction
 22030    E    ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE                        duplicate_json_object_key_value
+22031    E    ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION            invalid_argument_for_json_datetime_function
 22032    E    ERRCODE_INVALID_JSON_TEXT                                      invalid_json_text
 22033    E    ERRCODE_INVALID_SQL_JSON_SUBSCRIPT                             invalid_sql_json_subscript
 22034    E    ERRCODE_MORE_THAN_ONE_SQL_JSON_ITEM                            more_than_one_sql_json_item
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index cf1f4093513..d135e3647ce 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9326,6 +9326,28 @@
   proname => 'jsonb_path_match', prorettype => 'bool',
   proargtypes => 'jsonb jsonpath jsonb bool', prosrc => 'jsonb_path_match' },
 
+{ oid => '6015', descr => 'jsonpath exists test with timezone',
+  proname => 'jsonb_path_exists_tz', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_exists_tz' },
+{ oid => '6016', descr => 'jsonpath query with timezone',
+  proname => 'jsonb_path_query_tz', provolatile => 's',
+  prorows => '1000', proretset => 't',
+  prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_query_tz' },
+{ oid => '6017', descr => 'jsonpath query wrapped into array with timezone',
+  proname => 'jsonb_path_query_array_tz', provolatile => 's',
+  prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_query_array_tz' },
+{ oid => '6018', descr => 'jsonpath query first item with timezone',
+  proname => 'jsonb_path_query_first_tz', provolatile => 's',
+  prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_query_first_tz' },
+{ oid => '6019', descr => 'jsonpath match with timezone',
+  proname => 'jsonb_path_match_tz', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_match_tz' },
+
 { oid => '4010', descr => 'implementation of @? operator',
   proname => 'jsonb_path_exists_opr', prorettype => 'bool',
   proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_path_exists_opr' },
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 40ad5fda928..f6b17c8aa2f 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -79,6 +79,7 @@ typedef enum JsonPathItemType
 	jpiFloor,					/* .floor() item method */
 	jpiCeiling,					/* .ceiling() item method */
 	jpiDouble,					/* .double() item method */
+	jpiDatetime,				/* .datetime() item method */
 	jpiKeyValue,				/* .keyvalue() item method */
 	jpiSubscript,				/* array subscript: 'expr' or 'expr TO expr' */
 	jpiLast,					/* LAST array subscript */
diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
index 0202667a1f7..f40a97dc4e1 100644
--- a/src/test/regress/expected/jsonb_jsonpath.out
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -1658,6 +1658,532 @@ select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ lik
  "a\b"
 (1 row)
 
+select jsonb_path_query('null', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('true', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('1', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('[]', '$.datetime()');
+ jsonb_path_query 
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'strict $.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('{}', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('""', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  unrecognized datetime format
+HINT:  use datetime template argument for explicit format specification
+select jsonb_path_query('"12:34"', '$.datetime("aaa")');
+ERROR:  invalid datetime format separator: "a"
+select jsonb_path_query('"aaaa"', '$.datetime("HH24")');
+ERROR:  invalid value "aa" for "HH24"
+DETAIL:  Value must be an integer.
+select jsonb '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")';
+ ?column? 
+----------
+ t
+(1 row)
+
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+ jsonb_path_query 
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+ jsonb_path_query 
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+ERROR:  trailing characters remain in input string after datetime format
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+ERROR:  trailing characters remain in input string after datetime format
+select jsonb_path_query('"10-03-2017 12:34"', '       $.datetime("dd-mm-yyyy HH24:MI").type()');
+       jsonb_path_query        
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+      jsonb_path_query      
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+     jsonb_path_query     
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+   jsonb_path_query    
+-----------------------
+ "time with time zone"
+(1 row)
+
+set time zone '+00';
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+ jsonb_path_query 
+------------------
+ "12:34:00"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone '+10';
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+ jsonb_path_query 
+------------------
+ "12:34:00"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone default;
+select jsonb_path_query('"2017-03-10"', '$.datetime().type()');
+ jsonb_path_query 
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"2017-03-10"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+       jsonb_path_query        
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:34:56"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()');
+      jsonb_path_query      
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:56+03:00"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()');
+      jsonb_path_query      
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:56+03:10"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime().type()');
+     jsonb_path_query     
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "12:34:56"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()');
+   jsonb_path_query    
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "12:34:56+03:00"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()');
+   jsonb_path_query    
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "12:34:56+03:10"
+(1 row)
+
+set time zone '+00';
+-- date comparison
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR:  cannot convert value from date to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR:  cannot convert value from date to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR:  cannot convert value from date to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10"
+ "2017-03-10T00:00:00"
+ "2017-03-10T03:00:00+03:00"
+(3 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10"
+ "2017-03-11"
+ "2017-03-10T00:00:00"
+ "2017-03-10T12:34:56"
+ "2017-03-10T03:00:00+03:00"
+(5 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-09"
+ "2017-03-10T01:02:03+04:00"
+(2 rows)
+
+-- time comparison
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00"
+ "12:35:00+00:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00"
+ "12:36:00"
+ "12:35:00+00:00"
+(3 rows)
+
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:34:00"
+ "12:35:00+01:00"
+ "13:35:00+01:00"
+(3 rows)
+
+-- timetz comparison
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00+01:00"
+(1 row)
+
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00+01:00"
+ "12:36:00+01:00"
+ "12:35:00-02:00"
+ "11:35:00"
+ "12:35:00"
+(5 rows)
+
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:34:00+01:00"
+ "12:35:00+02:00"
+ "10:35:00"
+(3 rows)
+
+-- timestamp comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T13:35:00+01:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T12:36:00"
+ "2017-03-10T13:35:00+01:00"
+ "2017-03-10T12:35:00-01:00"
+ "2017-03-11"
+(5 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:34:00"
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10"
+(3 rows)
+
+-- timestamptz comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T11:35:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+set time zone default;
 -- jsonpath operators
 SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
  jsonb_path_query 
diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
index ecdd453942b..f5d5fa4fb8d 100644
--- a/src/test/regress/expected/jsonpath.out
+++ b/src/test/regress/expected/jsonpath.out
@@ -401,6 +401,18 @@ select '$.keyvalue().key'::jsonpath;
  $.keyvalue()."key"
 (1 row)
 
+select '$.datetime()'::jsonpath;
+   jsonpath   
+--------------
+ $.datetime()
+(1 row)
+
+select '$.datetime("datetime template")'::jsonpath;
+            jsonpath             
+---------------------------------
+ $.datetime("datetime template")
+(1 row)
+
 select '$ ? (@ starts with "abc")'::jsonpath;
         jsonpath         
 -------------------------
diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
index e7629fb7f9d..a6cb74798a6 100644
--- a/src/test/regress/sql/jsonb_jsonpath.sql
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -346,6 +346,178 @@ select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ lik
 select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\B$" flag "iq")');
 select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\b$" flag "")');
 
+select jsonb_path_query('null', '$.datetime()');
+select jsonb_path_query('true', '$.datetime()');
+select jsonb_path_query('1', '$.datetime()');
+select jsonb_path_query('[]', '$.datetime()');
+select jsonb_path_query('[]', 'strict $.datetime()');
+select jsonb_path_query('{}', '$.datetime()');
+select jsonb_path_query('""', '$.datetime()');
+select jsonb_path_query('"12:34"', '$.datetime("aaa")');
+select jsonb_path_query('"aaaa"', '$.datetime("HH24")');
+
+select jsonb '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")';
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+
+select jsonb_path_query('"10-03-2017 12:34"', '       $.datetime("dd-mm-yyyy HH24:MI").type()');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+
+set time zone '+00';
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone '+10';
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone default;
+
+select jsonb_path_query('"2017-03-10"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()');
+select jsonb_path_query('"12:34:56"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56"', '$.datetime()');
+select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56 +3"', '$.datetime()');
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()');
+
+set time zone '+00';
+
+-- date comparison
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+
+-- time comparison
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+
+-- timetz comparison
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+
+-- timestamp comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+
+-- timestamptz comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+
+set time zone default;
+
 -- jsonpath operators
 
 SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
index 29ea77a4858..713d32400a7 100644
--- a/src/test/regress/sql/jsonpath.sql
+++ b/src/test/regress/sql/jsonpath.sql
@@ -72,6 +72,8 @@ select '"aaa".type()'::jsonpath;
 select 'true.type()'::jsonpath;
 select '$.double().floor().ceiling().abs()'::jsonpath;
 select '$.keyvalue().key'::jsonpath;
+select '$.datetime()'::jsonpath;
+select '$.datetime("datetime template")'::jsonpath;
 
 select '$ ? (@ starts with "abc")'::jsonpath;
 select '$ ? (@ starts with $var)'::jsonpath;
#18Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Alexander Korotkov (#17)
8 attachment(s)
Re: Support for jsonpath .datetime() method

On Tue, Aug 27, 2019 at 5:19 AM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

Revised patchset is attached. It still requires some polishing. But
the most doubtful part is handling of RR, YYY, YY and Y.

Standard requires us to complete YYY, YY and Y with high digits from
current year. So, if YY matches 99, then year should be 2099, not
1999.

For RR, standard requirements are relaxed. Implementation may choose
matching year from range [current_year - 100; current_year + 100]. It
looks reasonable to handle RR in the same way we currently handle YY:
select appropriate year in [1970; 2069] range. It seems like we
select this range to start in the same point as unix timestamp. But
nowadays it still looks reasonable: it's about +- 50 from current
year. So, years close to the current one are likely completed
correctly. In Oracle RR returns year in [1950; 1949] range. So, it
seems to be designed near 2000 :). I don't think we need to copy this
behavior.

Handling YYY and YY in standard way seems quite easy. We can complete
them as 2YYY and 20YY. This should be standard conforming till 2100.

But handling Y looks problematic. Immutable way of handling this
would work only for decade. Current code completes Y as 200Y and it
looks pretty "outdated" now in 2019. Using current real year would
make conversion timestamp-dependent. This property doesn't look favor
for to_date()/to_timestamp() and unacceptable for immutable jsonpath
functions (but we can forbid using Y pattern there). Current patch
complete Y as 202Y assuming v13 will be released in 2020. But I'm not
sure what is better solution here. The bright side is that I haven't
seen anybody use Y patten in real life :)

Revised patchset is attached. It adds and adjusts commit messages,
comments and does other cosmetic improvements.

I think 0001 and 0002 are well reviewed already. And these patches
are usable not only for jsonpath .datetime(), but contain improvements
for existing to_date()/to_timestamp() SQL functions. I'm going to
push these two if no objections.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0002-Support-for-SSSSS-datetime-format-pattern-6.patchapplication/octet-stream; name=0002-Support-for-SSSSS-datetime-format-pattern-6.patchDownload
From 57222fa9b96e04595b549c9380ae907b5b7bf7e2 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Tue, 20 Aug 2019 08:38:20 +0300
Subject: [PATCH 2/8] Support for SSSSS datetime format pattern

SQL Standard 2016 defines SSSSS format pattern for seconds past midnight in
jsonpath .datetime() method and CAST (... FORMAT ...) SQL clause.  In our
datetime parsing engine we currently support it with SSSS name.

This commit adds SSSSS as an alias for SSSS.  Alias is added in favor of
upcoming jsonpath .datetime() method.  But it's also supported in to_date()/
to_timestamp() as positive side effect.

Discussion: https://postgr.es/m/CAPpHfdsZgYEra_PeCLGNoXOWYx6iU-S3wF8aX0ObQUcZU%2B4XTw%40mail.gmail.com
Author: Nikita Glukhov, Alexander Korotkov
Reviewed-by: Anastasia Lubennikova, Peter Eisentraut
---
 doc/src/sgml/func.sgml                 |  2 +-
 src/backend/utils/adt/formatting.c     | 12 ++++++++----
 src/test/regress/expected/horology.out | 20 ++++++++++++++++++++
 src/test/regress/sql/horology.sql      |  4 ++++
 4 files changed, 33 insertions(+), 5 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9c3b0e8a67b..0075df41c6a 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -6175,7 +6175,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
         <entry>microsecond (000000-999999)</entry>
        </row>
        <row>
-        <entry><literal>SSSS</literal></entry>
+        <entry><literal>SSSS</literal>, <literal>SSSSS</literal></entry>
         <entry>seconds past midnight (0-86399)</entry>
        </row>
        <row>
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 27ffe9ab392..d3c5619d584 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -626,6 +626,7 @@ typedef enum
 	DCH_PM,
 	DCH_Q,
 	DCH_RM,
+	DCH_SSSSS,
 	DCH_SSSS,
 	DCH_SS,
 	DCH_TZH,
@@ -678,6 +679,7 @@ typedef enum
 	DCH_pm,
 	DCH_q,
 	DCH_rm,
+	DCH_sssss,
 	DCH_ssss,
 	DCH_ss,
 	DCH_tz,
@@ -787,7 +789,8 @@ static const KeyWord DCH_keywords[] = {
 	{"PM", 2, DCH_PM, false, FROM_CHAR_DATE_NONE},
 	{"Q", 1, DCH_Q, true, FROM_CHAR_DATE_NONE}, /* Q */
 	{"RM", 2, DCH_RM, false, FROM_CHAR_DATE_GREGORIAN}, /* R */
-	{"SSSS", 4, DCH_SSSS, true, FROM_CHAR_DATE_NONE},	/* S */
+	{"SSSSS", 5, DCH_SSSS, true, FROM_CHAR_DATE_NONE},	/* S */
+	{"SSSS", 4, DCH_SSSS, true, FROM_CHAR_DATE_NONE},
 	{"SS", 2, DCH_SS, true, FROM_CHAR_DATE_NONE},
 	{"TZH", 3, DCH_TZH, false, FROM_CHAR_DATE_NONE},	/* T */
 	{"TZM", 3, DCH_TZM, true, FROM_CHAR_DATE_NONE},
@@ -839,7 +842,8 @@ static const KeyWord DCH_keywords[] = {
 	{"pm", 2, DCH_pm, false, FROM_CHAR_DATE_NONE},
 	{"q", 1, DCH_Q, true, FROM_CHAR_DATE_NONE}, /* q */
 	{"rm", 2, DCH_rm, false, FROM_CHAR_DATE_GREGORIAN}, /* r */
-	{"ssss", 4, DCH_SSSS, true, FROM_CHAR_DATE_NONE},	/* s */
+	{"sssss", 5, DCH_SSSS, true, FROM_CHAR_DATE_NONE},	/* s */
+	{"ssss", 4, DCH_SSSS, true, FROM_CHAR_DATE_NONE},
 	{"ss", 2, DCH_SS, true, FROM_CHAR_DATE_NONE},
 	{"tz", 2, DCH_tz, false, FROM_CHAR_DATE_NONE},	/* t */
 	{"us", 2, DCH_US, true, FROM_CHAR_DATE_NONE},	/* u */
@@ -920,10 +924,10 @@ static const int DCH_index[KeyWord_INDEX_SIZE] = {
 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 	-1, -1, -1, -1, -1, DCH_A_D, DCH_B_C, DCH_CC, DCH_DAY, -1,
 	DCH_FF1, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
-	DCH_P_M, DCH_Q, DCH_RM, DCH_SSSS, DCH_TZH, DCH_US, -1, DCH_WW, -1, DCH_Y_YYY,
+	DCH_P_M, DCH_Q, DCH_RM, DCH_SSSSS, DCH_TZH, DCH_US, -1, DCH_WW, -1, DCH_Y_YYY,
 	-1, -1, -1, -1, -1, -1, -1, DCH_a_d, DCH_b_c, DCH_cc,
 	DCH_day, -1, DCH_ff1, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
-	-1, -1, DCH_p_m, DCH_q, DCH_rm, DCH_ssss, DCH_tz, DCH_us, -1, DCH_ww,
+	-1, -1, DCH_p_m, DCH_q, DCH_rm, DCH_sssss, DCH_tz, DCH_us, -1, DCH_ww,
 	-1, DCH_y_yyy, -1, -1, -1, -1
 
 	/*---- chars over 126 are skipped ----*/
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index 74ecb7c10e6..6b53876e062 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -3077,6 +3077,14 @@ SELECT to_timestamp('2015-02-11 86000', 'YYYY-MM-DD SSSS');  -- ok
 
 SELECT to_timestamp('2015-02-11 86400', 'YYYY-MM-DD SSSS');
 ERROR:  date/time field value out of range: "2015-02-11 86400"
+SELECT to_timestamp('2015-02-11 86000', 'YYYY-MM-DD SSSSS');  -- ok
+         to_timestamp         
+------------------------------
+ Wed Feb 11 23:53:20 2015 PST
+(1 row)
+
+SELECT to_timestamp('2015-02-11 86400', 'YYYY-MM-DD SSSSS');
+ERROR:  date/time field value out of range: "2015-02-11 86400"
 SELECT to_date('2016-13-10', 'YYYY-MM-DD');
 ERROR:  date/time field value out of range: "2016-13-10"
 SELECT to_date('2016-02-30', 'YYYY-MM-DD');
@@ -3140,4 +3148,16 @@ SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD HH:MI:SS TZ');
  2012-12-12 12:00:00 -01:30
 (1 row)
 
+SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD SSSS');
+     to_char      
+------------------
+ 2012-12-12 43200
+(1 row)
+
+SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD SSSSS');
+     to_char      
+------------------
+ 2012-12-12 43200
+(1 row)
+
 RESET TIME ZONE;
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index 3c8580397ac..f7a9da1e954 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -480,6 +480,8 @@ SELECT to_timestamp('2016-02-29 15:50:55', 'YYYY-MM-DD HH24:MI:SS');  -- ok
 SELECT to_timestamp('2015-02-29 15:50:55', 'YYYY-MM-DD HH24:MI:SS');
 SELECT to_timestamp('2015-02-11 86000', 'YYYY-MM-DD SSSS');  -- ok
 SELECT to_timestamp('2015-02-11 86400', 'YYYY-MM-DD SSSS');
+SELECT to_timestamp('2015-02-11 86000', 'YYYY-MM-DD SSSSS');  -- ok
+SELECT to_timestamp('2015-02-11 86400', 'YYYY-MM-DD SSSSS');
 SELECT to_date('2016-13-10', 'YYYY-MM-DD');
 SELECT to_date('2016-02-30', 'YYYY-MM-DD');
 SELECT to_date('2016-02-29', 'YYYY-MM-DD');  -- ok
@@ -503,5 +505,7 @@ SELECT '2012-12-12 12:00'::timestamptz;
 SELECT '2012-12-12 12:00 America/New_York'::timestamptz;
 
 SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD HH:MI:SS TZ');
+SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD SSSS');
+SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD SSSSS');
 
 RESET TIME ZONE;
-- 
2.14.3

0001-Support-for-FF1-FF6-datetime-format-patterns-6.patchapplication/octet-stream; name=0001-Support-for-FF1-FF6-datetime-format-patterns-6.patchDownload
From 0d5e6879ce9e29e960627eb440cff501f7449b10 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Sun, 21 Jul 2019 01:12:16 +0300
Subject: [PATCH 1/8] Support for FF1-FF6 datetime format patterns

SQL Standard 2016 defines FF1-FF9 format patters for fractions of seconds in
jsonpath .datetime() method and CAST (... FORMAT ...) SQL clause.  Parsing
engine of upcoming .datetime() method will be shared with to_date()/
to_timestamp().

This patch implements FF1-FF6 format patterns for upcoming jsonpath .datetime()
method.  to_date()/to_timestamp() functions will also get support of this
format patterns as positive side effect.  FF7-FF9 are not supported due to
lack of precision in our internal timestamp representation.

Extracted from original patch by Nikita Glukhov, Teodor Sigaev, Oleg Bartunov.
Heavily revised by me.

Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
Discussion: https://postgr.es/m/CAPpHfdsZgYEra_PeCLGNoXOWYx6iU-S3wF8aX0ObQUcZU%2B4XTw%40mail.gmail.com
Author: Nikita Glukhov, Teodor Sigaev, Oleg Bartunov, Alexander Korotkov
Reviewed-by: Anastasia Lubennikova, Peter Eisentraut
---
 doc/src/sgml/func.sgml                    | 24 ++++++++
 src/backend/utils/adt/formatting.c        | 99 ++++++++++++++++++++++++-------
 src/backend/utils/adt/timestamp.c         |  3 +-
 src/include/utils/datetime.h              |  2 +
 src/test/regress/expected/horology.out    | 79 ++++++++++++++++++++++++
 src/test/regress/expected/timestamp.out   | 15 +++++
 src/test/regress/expected/timestamptz.out | 15 +++++
 src/test/regress/sql/horology.sql         |  9 +++
 src/test/regress/sql/timestamp.sql        |  8 +++
 src/test/regress/sql/timestamptz.sql      |  8 +++
 10 files changed, 239 insertions(+), 23 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index cdf41ddef44..9c3b0e8a67b 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -6150,6 +6150,30 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
         <entry><literal>US</literal></entry>
         <entry>microsecond (000000-999999)</entry>
        </row>
+       <row>
+        <entry><literal>FF1</literal></entry>
+        <entry>decisecond (0-9)</entry>
+       </row>
+       <row>
+        <entry><literal>FF2</literal></entry>
+        <entry>centisecond (00-99)</entry>
+       </row>
+       <row>
+        <entry><literal>FF3</literal></entry>
+        <entry>millisecond (000-999)</entry>
+       </row>
+       <row>
+        <entry><literal>FF4</literal></entry>
+        <entry>tenth of a millisecond (0000-9999)</entry>
+       </row>
+       <row>
+        <entry><literal>FF5</literal></entry>
+        <entry>hundredth of a millisecond (00000-99999)</entry>
+       </row>
+       <row>
+        <entry><literal>FF6</literal></entry>
+        <entry>microsecond (000000-999999)</entry>
+       </row>
        <row>
         <entry><literal>SSSS</literal></entry>
         <entry>seconds past midnight (0-86399)</entry>
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 755ca6e277c..27ffe9ab392 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -86,6 +86,7 @@
 #endif
 
 #include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
 #include "mb/pg_wchar.h"
 #include "utils/builtins.h"
 #include "utils/date.h"
@@ -434,7 +435,8 @@ typedef struct
 				clock,			/* 12 or 24 hour clock? */
 				tzsign,			/* +1, -1 or 0 if timezone info is absent */
 				tzh,
-				tzm;
+				tzm,
+				ff;				/* fractional precision */
 } TmFromChar;
 
 #define ZERO_tmfc(_X) memset(_X, 0, sizeof(TmFromChar))
@@ -594,6 +596,12 @@ typedef enum
 	DCH_Day,
 	DCH_Dy,
 	DCH_D,
+	DCH_FF1,
+	DCH_FF2,
+	DCH_FF3,
+	DCH_FF4,
+	DCH_FF5,
+	DCH_FF6,
 	DCH_FX,						/* global suffix */
 	DCH_HH24,
 	DCH_HH12,
@@ -643,6 +651,12 @@ typedef enum
 	DCH_dd,
 	DCH_dy,
 	DCH_d,
+	DCH_ff1,
+	DCH_ff2,
+	DCH_ff3,
+	DCH_ff4,
+	DCH_ff5,
+	DCH_ff6,
 	DCH_fx,
 	DCH_hh24,
 	DCH_hh12,
@@ -743,7 +757,13 @@ static const KeyWord DCH_keywords[] = {
 	{"Day", 3, DCH_Day, false, FROM_CHAR_DATE_NONE},
 	{"Dy", 2, DCH_Dy, false, FROM_CHAR_DATE_NONE},
 	{"D", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
-	{"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},	/* F */
+	{"FF1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE},	/* F */
+	{"FF2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE},
+	{"FF3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE},
+	{"FF4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE},
+	{"FF5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE},
+	{"FF6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE},
+	{"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
 	{"HH24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE},	/* H */
 	{"HH12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
 	{"HH", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
@@ -792,7 +812,13 @@ static const KeyWord DCH_keywords[] = {
 	{"dd", 2, DCH_DD, true, FROM_CHAR_DATE_GREGORIAN},
 	{"dy", 2, DCH_dy, false, FROM_CHAR_DATE_NONE},
 	{"d", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
-	{"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},	/* f */
+	{"ff1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE},	/* f */
+	{"ff2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE},
+	{"ff3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE},
+	{"ff4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE},
+	{"ff5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE},
+	{"ff6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE},
+	{"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
 	{"hh24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE},	/* h */
 	{"hh12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
 	{"hh", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
@@ -893,10 +919,10 @@ static const int DCH_index[KeyWord_INDEX_SIZE] = {
 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 	-1, -1, -1, -1, -1, DCH_A_D, DCH_B_C, DCH_CC, DCH_DAY, -1,
-	DCH_FX, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
+	DCH_FF1, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
 	DCH_P_M, DCH_Q, DCH_RM, DCH_SSSS, DCH_TZH, DCH_US, -1, DCH_WW, -1, DCH_Y_YYY,
 	-1, -1, -1, -1, -1, -1, -1, DCH_a_d, DCH_b_c, DCH_cc,
-	DCH_day, -1, DCH_fx, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
+	DCH_day, -1, DCH_ff1, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
 	-1, -1, DCH_p_m, DCH_q, DCH_rm, DCH_ssss, DCH_tz, DCH_us, -1, DCH_ww,
 	-1, DCH_y_yyy, -1, -1, -1, -1
 
@@ -960,7 +986,6 @@ typedef struct NUMProc
 			   *L_currency_symbol;
 } NUMProc;
 
-
 /* ----------
  * Functions
  * ----------
@@ -993,7 +1018,7 @@ static int	from_char_parse_int(int *dest, char **src, FormatNode *node);
 static int	seq_search(char *name, const char *const *array, int type, int max, int *len);
 static int	from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
 static void do_to_timestamp(text *date_txt, text *fmt,
-							struct pg_tm *tm, fsec_t *fsec);
+							struct pg_tm *tm, fsec_t *fsec, int *fprec);
 static char *fill_str(char *str, int c, int max);
 static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
 static char *int_to_roman(int number);
@@ -2518,18 +2543,32 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 					str_numth(s, s, S_TH_TYPE(n->suffix));
 				s += strlen(s);
 				break;
-			case DCH_MS:		/* millisecond */
-				sprintf(s, "%03d", (int) (in->fsec / INT64CONST(1000)));
-				if (S_THth(n->suffix))
-					str_numth(s, s, S_TH_TYPE(n->suffix));
+#define DCH_to_char_fsec(frac_fmt, frac_val) \
+				sprintf(s, frac_fmt, (int) (frac_val)); \
+				if (S_THth(n->suffix)) \
+					str_numth(s, s, S_TH_TYPE(n->suffix)); \
 				s += strlen(s);
+			case DCH_FF1:		/* decisecond */
+				DCH_to_char_fsec("%01d", in->fsec / 100000);
+				break;
+			case DCH_FF2:		/* centisecond */
+				DCH_to_char_fsec("%02d", in->fsec / 10000);
+				break;
+			case DCH_FF3:
+			case DCH_MS:		/* millisecond */
+				DCH_to_char_fsec("%03d", in->fsec / 1000);
 				break;
+			case DCH_FF4:
+				DCH_to_char_fsec("%04d", in->fsec / 100);
+				break;
+			case DCH_FF5:
+				DCH_to_char_fsec("%05d", in->fsec / 10);
+				break;
+			case DCH_FF6:
 			case DCH_US:		/* microsecond */
-				sprintf(s, "%06d", (int) in->fsec);
-				if (S_THth(n->suffix))
-					str_numth(s, s, S_TH_TYPE(n->suffix));
-				s += strlen(s);
+				DCH_to_char_fsec("%06d", in->fsec);
 				break;
+#undef DCH_to_char_fsec
 			case DCH_SSSS:
 				sprintf(s, "%d", tm->tm_hour * SECS_PER_HOUR +
 						tm->tm_min * SECS_PER_MINUTE +
@@ -3154,8 +3193,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 
 				SKIP_THth(s, n->suffix);
 				break;
+			case DCH_FF1:
+			case DCH_FF2:
+			case DCH_FF3:
+			case DCH_FF4:
+			case DCH_FF5:
+			case DCH_FF6:
+				out->ff = n->key->id - DCH_FF1 + 1;
+				/* fall through */
 			case DCH_US:		/* microsecond */
-				len = from_char_parse_int_len(&out->us, &s, 6, n);
+				len = from_char_parse_int_len(&out->us, &s,
+											  n->key->id == DCH_US ? 6 :
+											  out->ff, n);
 
 				out->us *= len == 1 ? 100000 :
 					len == 2 ? 10000 :
@@ -3689,8 +3738,9 @@ to_timestamp(PG_FUNCTION_ARGS)
 	int			tz;
 	struct pg_tm tm;
 	fsec_t		fsec;
+	int			fprec;
 
-	do_to_timestamp(date_txt, fmt, &tm, &fsec);
+	do_to_timestamp(date_txt, fmt, &tm, &fsec, &fprec);
 
 	/* Use the specified time zone, if any. */
 	if (tm.tm_zone)
@@ -3708,6 +3758,10 @@ to_timestamp(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 				 errmsg("timestamp out of range")));
 
+	/* Use the specified fractional precision, if any. */
+	if (fprec)
+		AdjustTimestampForTypmod(&result, fprec);
+
 	PG_RETURN_TIMESTAMP(result);
 }
 
@@ -3725,7 +3779,7 @@ to_date(PG_FUNCTION_ARGS)
 	struct pg_tm tm;
 	fsec_t		fsec;
 
-	do_to_timestamp(date_txt, fmt, &tm, &fsec);
+	do_to_timestamp(date_txt, fmt, &tm, &fsec, NULL);
 
 	/* Prevent overflow in Julian-day routines */
 	if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3749,8 +3803,8 @@ to_date(PG_FUNCTION_ARGS)
 /*
  * do_to_timestamp: shared code for to_timestamp and to_date
  *
- * Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm
- * and fractional seconds.
+ * Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm,
+ * fractional seconds, and fractional precision.
  *
  * We parse 'fmt' into a list of FormatNodes, which is then passed to
  * DCH_from_char to populate a TmFromChar with the parsed contents of
@@ -3761,7 +3815,7 @@ to_date(PG_FUNCTION_ARGS)
  */
 static void
 do_to_timestamp(text *date_txt, text *fmt,
-				struct pg_tm *tm, fsec_t *fsec)
+				struct pg_tm *tm, fsec_t *fsec, int *fprec)
 {
 	FormatNode *format;
 	TmFromChar	tmfc;
@@ -3817,6 +3871,7 @@ do_to_timestamp(text *date_txt, text *fmt,
 		DCH_from_char(format, date_str, &tmfc);
 
 		pfree(fmt_str);
+
 		if (!incache)
 			pfree(format);
 	}
@@ -3998,6 +4053,8 @@ do_to_timestamp(text *date_txt, text *fmt,
 		*fsec += tmfc.ms * 1000;
 	if (tmfc.us)
 		*fsec += tmfc.us;
+	if (fprec)
+		*fprec = tmfc.ff;		/* fractional precision, if specified */
 
 	/* Range-check date fields according to bit mask computed above */
 	if (fmask != 0)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 5861ffbbc97..2931bd58c0d 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -70,7 +70,6 @@ typedef struct
 
 static TimeOffset time2t(const int hour, const int min, const int sec, const fsec_t fsec);
 static Timestamp dt2local(Timestamp dt, int timezone);
-static void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
 static void AdjustIntervalForTypmod(Interval *interval, int32 typmod);
 static TimestampTz timestamp2timestamptz(Timestamp timestamp);
 static Timestamp timestamptz2timestamp(TimestampTz timestamp);
@@ -333,7 +332,7 @@ timestamp_scale(PG_FUNCTION_ARGS)
  * AdjustTimestampForTypmod --- round off a timestamp to suit given typmod
  * Works for either timestamp or timestamptz.
  */
-static void
+void
 AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
 {
 	static const int64 TimestampScales[MAX_TIMESTAMP_PRECISION + 1] = {
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index b8a199cdded..0cafdd26538 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -336,4 +336,6 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
 												   int n);
 extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
 
+extern void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
+
 #endif							/* DATETIME_H */
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index b2b45773339..74ecb7c10e6 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -2786,6 +2786,85 @@ SELECT to_timestamp('2011-12-18 11:38 20',     'YYYY-MM-DD HH12:MI TZM');
  Sun Dec 18 03:18:00 2011 PST
 (1 row)
 
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |         to_timestamp         
+---+------------------------------
+ 1 | Fri Nov 02 12:34:56 2018 PDT
+ 2 | Fri Nov 02 12:34:56 2018 PDT
+ 3 | Fri Nov 02 12:34:56 2018 PDT
+ 4 | Fri Nov 02 12:34:56 2018 PDT
+ 5 | Fri Nov 02 12:34:56 2018 PDT
+ 6 | Fri Nov 02 12:34:56 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |          to_timestamp          
+---+--------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.1 2018 PDT
+ 3 | Fri Nov 02 12:34:56.1 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |          to_timestamp           
+---+---------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.12 2018 PDT
+ 4 | Fri Nov 02 12:34:56.12 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |           to_timestamp           
+---+----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.123 2018 PDT
+ 5 | Fri Nov 02 12:34:56.123 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |           to_timestamp            
+---+-----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1234 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |            to_timestamp            
+---+------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12345 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12345 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |            to_timestamp             
+---+-------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12346 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123456 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ERROR:  date/time field value out of range: "2018-11-02 12:34:56.123456789"
 --
 -- Check handling of multiple spaces in format and/or input
 --
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index 715680e3302..f772b07d5a4 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -1584,6 +1584,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
             | 2001 1 1 1 1 1 1
 (65 rows)
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamp),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+ to_char_12 |                              to_char                               
+------------+--------------------------------------------------------------------
+            | 0 00 000 0000 00000 000000  0 00 000 0000 00000 000000  000 000000
+            | 7 78 780 7800 78000 780000  7 78 780 7800 78000 780000  780 780000
+            | 7 78 789 7890 78901 789010  7 78 789 7890 78901 789010  789 789010
+            | 7 78 789 7890 78901 789012  7 78 789 7890 78901 789012  789 789012
+(4 rows)
+
 -- timestamp numeric fields constructor
 SELECT make_timestamp(2014,12,28,6,30,45.887);
         make_timestamp        
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 5551fa6610e..2d6a71ca64b 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -1704,6 +1704,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
             | 2001 1 1 1 1 1 1
 (66 rows)
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamptz),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+ to_char_12 |                              to_char                               
+------------+--------------------------------------------------------------------
+            | 0 00 000 0000 00000 000000  0 00 000 0000 00000 000000  000 000000
+            | 7 78 780 7800 78000 780000  7 78 780 7800 78000 780000  780 780000
+            | 7 78 789 7890 78901 789010  7 78 789 7890 78901 789010  789 789010
+            | 7 78 789 7890 78901 789012  7 78 789 7890 78901 789012  789 789012
+(4 rows)
+
 -- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
 SET timezone = '00:00';
 SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index e356dd563ee..3c8580397ac 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -402,6 +402,15 @@ SELECT to_timestamp('2011-12-18 11:38 +05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
 SELECT to_timestamp('2011-12-18 11:38 -05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
 SELECT to_timestamp('2011-12-18 11:38 20',     'YYYY-MM-DD HH12:MI TZM');
 
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+
 --
 -- Check handling of multiple spaces in format and/or input
 --
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index 031b22bc3c1..329987f7eaa 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -224,5 +224,13 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
 SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
    FROM TIMESTAMP_TBL;
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamp),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+
 -- timestamp numeric fields constructor
 SELECT make_timestamp(2014,12,28,6,30,45.887);
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index 28c76d6b72c..f5fee639a01 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -248,6 +248,14 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
 SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
    FROM TIMESTAMPTZ_TBL;
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamptz),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+
 -- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
 SET timezone = '00:00';
 SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
-- 
2.14.3

0004-Implement-standard-datetime-parsing-mode-6.patchapplication/octet-stream; name=0004-Implement-standard-datetime-parsing-mode-6.patchDownload
From 983244b09e4f79fdf56dbbdf8fb62573e07e34fa Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Mon, 26 Aug 2019 04:40:24 +0300
Subject: [PATCH 4/8] Implement standard datetime parsing mode

SQL Standard 2016 defines rules for handling separators in datetime template
strings, which are different to to_date()/to_timestamp() rules.  Stardard
allows only small set of separators and requires strict matching for them.

Standard applies to jsonpath .datetime() method and CAST (... FORMAT ...) SQL
clause.  We're not going to change handling of separators in existing
to_date()/to_timestamp() functions, because their current behavior is familiar
for users.  Standard behavior now available by special flag, which will be used
in upcoming .datetime() jsonpath method.

Discussion: https://postgr.es/m/CAPpHfdsZgYEra_PeCLGNoXOWYx6iU-S3wF8aX0ObQUcZU%2B4XTw%40mail.gmail.com
Author: Alexander Korotkov
---
 src/backend/utils/adt/formatting.c | 145 +++++++++++++++++++++++++++----------
 1 file changed, 105 insertions(+), 40 deletions(-)

diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 673a2cff192..93f99fc7507 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -99,11 +99,12 @@
 #include "utils/pg_locale.h"
 
 /* ----------
- * Routines type
+ * Routines flags
  * ----------
  */
-#define DCH_TYPE		1		/* DATE-TIME version	*/
-#define NUM_TYPE		2		/* NUMBER version	*/
+#define DCH_FLAG		0x1		/* DATE-TIME flag	*/
+#define NUM_FLAG		0x2		/* NUMBER flag	*/
+#define STD_FLAG		0x4		/* STANDARD flag	*/
 
 /* ----------
  * KeyWord Index (ascii from position 32 (' ') to 126 (~))
@@ -384,6 +385,7 @@ typedef struct
 {
 	FormatNode	format[DCH_CACHE_SIZE + 1];
 	char		str[DCH_CACHE_SIZE + 1];
+	bool		std;
 	bool		valid;
 	int			age;
 } DCHCacheEntry;
@@ -1008,11 +1010,12 @@ static const KeySuffix *suff_search(const char *str, const KeySuffix *suf, int t
 static bool is_separator_char(const char *str);
 static void NUMDesc_prepare(NUMDesc *num, FormatNode *n);
 static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
-						 const KeySuffix *suf, const int *index, int ver, NUMDesc *Num);
+						 const KeySuffix *suf, const int *index, uint32 flags, NUMDesc *Num);
 
 static void DCH_to_char(FormatNode *node, bool is_interval,
 						TmToChar *in, char *out, Oid collid);
-static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out);
+static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
+						  bool std);
 
 #ifdef DEBUG_TO_FROM_CHAR
 static void dump_index(const KeyWord *k, const int *index);
@@ -1030,7 +1033,7 @@ static int	from_char_parse_int_len(int *dest, char **src, const int len, FormatN
 static int	from_char_parse_int(int *dest, char **src, FormatNode *node);
 static int	seq_search(char *name, const char *const *array, int type, int max, int *len);
 static int	from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
-static void do_to_timestamp(text *date_txt, text *fmt,
+static void do_to_timestamp(text *date_txt, text *fmt, bool std,
 							struct pg_tm *tm, fsec_t *fsec, int *fprec);
 static char *fill_str(char *str, int c, int max);
 static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
@@ -1042,9 +1045,9 @@ static void NUM_numpart_to_char(NUMProc *Np, int id);
 static char *NUM_processor(FormatNode *node, NUMDesc *Num, char *inout,
 						   char *number, int input_len, int to_char_out_pre_spaces,
 						   int sign, bool is_to_char, Oid collid);
-static DCHCacheEntry *DCH_cache_getnew(const char *str);
-static DCHCacheEntry *DCH_cache_search(const char *str);
-static DCHCacheEntry *DCH_cache_fetch(const char *str);
+static DCHCacheEntry *DCH_cache_getnew(const char *str, bool std);
+static DCHCacheEntry *DCH_cache_search(const char *str, bool std);
+static DCHCacheEntry *DCH_cache_fetch(const char *str, bool std);
 static NUMCacheEntry *NUM_cache_getnew(const char *str);
 static NUMCacheEntry *NUM_cache_search(const char *str);
 static NUMCacheEntry *NUM_cache_fetch(const char *str);
@@ -1287,7 +1290,7 @@ NUMDesc_prepare(NUMDesc *num, FormatNode *n)
  */
 static void
 parse_format(FormatNode *node, const char *str, const KeyWord *kw,
-			 const KeySuffix *suf, const int *index, int ver, NUMDesc *Num)
+			 const KeySuffix *suf, const int *index, uint32 flags, NUMDesc *Num)
 {
 	FormatNode *n;
 
@@ -1305,7 +1308,7 @@ parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 		/*
 		 * Prefix
 		 */
-		if (ver == DCH_TYPE &&
+		if ((flags & DCH_FLAG) &&
 			(s = suff_search(str, suf, SUFFTYPE_PREFIX)) != NULL)
 		{
 			suffix |= s->id;
@@ -1326,13 +1329,13 @@ parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 			/*
 			 * NUM version: Prepare global NUMDesc struct
 			 */
-			if (ver == NUM_TYPE)
+			if (flags & NUM_FLAG)
 				NUMDesc_prepare(Num, n);
 
 			/*
 			 * Postfix
 			 */
-			if (ver == DCH_TYPE && *str &&
+			if ((flags & DCH_FLAG) && *str &&
 				(s = suff_search(str, suf, SUFFTYPE_POSTFIX)) != NULL)
 			{
 				n->suffix |= s->id;
@@ -1346,11 +1349,35 @@ parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 		{
 			int			chlen;
 
-			/*
-			 * Process double-quoted literal string, if any
-			 */
-			if (*str == '"')
+			if (flags & STD_FLAG)
+			{
+				/*
+				 * Standard mode, allow only following separators:
+				 * "-./,':; "
+				 */
+				if (strchr("-./,':; ", *str) == NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							 errmsg("invalid datetime format separator: \"%s\"",
+									pnstrdup(str, pg_mblen(str)))));
+
+				if (*str == ' ')
+					n->type = NODE_TYPE_SPACE;
+				else
+					n->type = NODE_TYPE_SEPARATOR;
+
+				n->character[0] = *str;
+				n->character[1] = '\0';
+				n->key = NULL;
+				n->suffix = 0;
+				n++;
+				str++;
+			}
+			else if (*str == '"')
 			{
+				/*
+				 * Process double-quoted literal string, if any
+				 */
 				str++;
 				while (*str)
 				{
@@ -1382,7 +1409,7 @@ parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 					str++;
 				chlen = pg_mblen(str);
 
-				if (ver == DCH_TYPE && is_separator_char(str))
+				if ((flags & DCH_FLAG) && is_separator_char(str))
 					n->type = NODE_TYPE_SEPARATOR;
 				else if (isspace((unsigned char) *str))
 					n->type = NODE_TYPE_SPACE;
@@ -3084,13 +3111,13 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
  * ----------
  */
 static void
-DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
+DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 {
 	FormatNode *n;
 	char	   *s;
 	int			len,
 				value;
-	bool		fx_mode = false;
+	bool		fx_mode = std;
 
 	/* number of extra skipped characters (more than given in format string) */
 	int			extra_skip = 0;
@@ -3113,7 +3140,23 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 
 		if (n->type == NODE_TYPE_SPACE || n->type == NODE_TYPE_SEPARATOR)
 		{
-			if (!fx_mode)
+			if (std)
+			{
+				/*
+				 * Standard mode requires strict matching between format string
+				 * separators/spaces and input string.
+				 */
+				Assert(n->character[0] && !n->character[1]);
+
+				if (*s == n->character[0])
+					s++;
+				else
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							 errmsg("unmatched format separator \"%c\"",
+									n->character[0])));
+			}
+			else if (!fx_mode)
 			{
 				/*
 				 * In non FX (fixed format) mode one format string space or
@@ -3465,6 +3508,27 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 			}
 		}
 	}
+
+	/*
+	 * Standard parsing mode doesn't allow unmatched format patterns or
+	 * trailing characters in the input string.
+	 */
+	if (std)
+	{
+		if (n->type != NODE_TYPE_END)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+					 errmsg("input string is too short for datetime format")));
+
+		while (*s != '\0' && isspace((unsigned char) *s))
+			s++;
+
+		if (*s != '\0')
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+					 errmsg("trailing characters remain in input string after "
+							"datetime format")));
+	}
 }
 
 /*
@@ -3487,7 +3551,7 @@ DCH_prevent_counter_overflow(void)
 
 /* select a DCHCacheEntry to hold the given format picture */
 static DCHCacheEntry *
-DCH_cache_getnew(const char *str)
+DCH_cache_getnew(const char *str, bool std)
 {
 	DCHCacheEntry *ent;
 
@@ -3537,6 +3601,7 @@ DCH_cache_getnew(const char *str)
 			MemoryContextAllocZero(TopMemoryContext, sizeof(DCHCacheEntry));
 		ent->valid = false;
 		StrNCpy(ent->str, str, DCH_CACHE_SIZE + 1);
+		ent->std = std;
 		ent->age = (++DCHCounter);
 		/* caller is expected to fill format, then set valid */
 		++n_DCHCache;
@@ -3546,7 +3611,7 @@ DCH_cache_getnew(const char *str)
 
 /* look for an existing DCHCacheEntry matching the given format picture */
 static DCHCacheEntry *
-DCH_cache_search(const char *str)
+DCH_cache_search(const char *str, bool std)
 {
 	/* Ensure we can advance DCHCounter below */
 	DCH_prevent_counter_overflow();
@@ -3555,7 +3620,7 @@ DCH_cache_search(const char *str)
 	{
 		DCHCacheEntry *ent = DCHCache[i];
 
-		if (ent->valid && strcmp(ent->str, str) == 0)
+		if (ent->valid && strcmp(ent->str, str) == 0 && ent->std == std)
 		{
 			ent->age = (++DCHCounter);
 			return ent;
@@ -3567,21 +3632,21 @@ DCH_cache_search(const char *str)
 
 /* Find or create a DCHCacheEntry for the given format picture */
 static DCHCacheEntry *
-DCH_cache_fetch(const char *str)
+DCH_cache_fetch(const char *str, bool std)
 {
 	DCHCacheEntry *ent;
 
-	if ((ent = DCH_cache_search(str)) == NULL)
+	if ((ent = DCH_cache_search(str, std)) == NULL)
 	{
 		/*
 		 * Not in the cache, must run parser and save a new format-picture to
 		 * the cache.  Do not mark the cache entry valid until parsing
 		 * succeeds.
 		 */
-		ent = DCH_cache_getnew(str);
+		ent = DCH_cache_getnew(str, std);
 
-		parse_format(ent->format, str, DCH_keywords,
-					 DCH_suff, DCH_index, DCH_TYPE, NULL);
+		parse_format(ent->format, str, DCH_keywords, DCH_suff, DCH_index,
+					 DCH_FLAG | (std ? STD_FLAG : 0), NULL);
 
 		ent->valid = true;
 	}
@@ -3626,14 +3691,14 @@ datetime_to_char_body(TmToChar *tmtc, text *fmt, bool is_interval, Oid collid)
 		format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode));
 
 		parse_format(format, fmt_str, DCH_keywords,
-					 DCH_suff, DCH_index, DCH_TYPE, NULL);
+					 DCH_suff, DCH_index, DCH_FLAG, NULL);
 	}
 	else
 	{
 		/*
 		 * Use cache buffers
 		 */
-		DCHCacheEntry *ent = DCH_cache_fetch(fmt_str);
+		DCHCacheEntry *ent = DCH_cache_fetch(fmt_str, false);
 
 		incache = true;
 		format = ent->format;
@@ -3775,7 +3840,7 @@ to_timestamp(PG_FUNCTION_ARGS)
 	fsec_t		fsec;
 	int			fprec;
 
-	do_to_timestamp(date_txt, fmt, &tm, &fsec, &fprec);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec);
 
 	/* Use the specified time zone, if any. */
 	if (tm.tm_zone)
@@ -3814,7 +3879,7 @@ to_date(PG_FUNCTION_ARGS)
 	struct pg_tm tm;
 	fsec_t		fsec;
 
-	do_to_timestamp(date_txt, fmt, &tm, &fsec, NULL);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL);
 
 	/* Prevent overflow in Julian-day routines */
 	if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3849,7 +3914,7 @@ to_date(PG_FUNCTION_ARGS)
  * struct 'tm' and 'fsec'.
  */
 static void
-do_to_timestamp(text *date_txt, text *fmt,
+do_to_timestamp(text *date_txt, text *fmt, bool std,
 				struct pg_tm *tm, fsec_t *fsec, int *fprec)
 {
 	FormatNode *format;
@@ -3884,15 +3949,15 @@ do_to_timestamp(text *date_txt, text *fmt,
 
 			format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode));
 
-			parse_format(format, fmt_str, DCH_keywords,
-						 DCH_suff, DCH_index, DCH_TYPE, NULL);
+			parse_format(format, fmt_str, DCH_keywords, DCH_suff, DCH_index,
+						 DCH_FLAG | (std ? STD_FLAG : 0), NULL);
 		}
 		else
 		{
 			/*
 			 * Use cache buffers
 			 */
-			DCHCacheEntry *ent = DCH_cache_fetch(fmt_str);
+			DCHCacheEntry *ent = DCH_cache_fetch(fmt_str, std);
 
 			incache = true;
 			format = ent->format;
@@ -3903,7 +3968,7 @@ do_to_timestamp(text *date_txt, text *fmt,
 		/* dump_index(DCH_keywords, DCH_index); */
 #endif
 
-		DCH_from_char(format, date_str, &tmfc);
+		DCH_from_char(format, date_str, &tmfc, std);
 
 		pfree(fmt_str);
 
@@ -4272,7 +4337,7 @@ NUM_cache_fetch(const char *str)
 		zeroize_NUM(&ent->Num);
 
 		parse_format(ent->format, str, NUM_keywords,
-					 NULL, NUM_index, NUM_TYPE, &ent->Num);
+					 NULL, NUM_index, NUM_FLAG, &ent->Num);
 
 		ent->valid = true;
 	}
@@ -4304,7 +4369,7 @@ NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree)
 		zeroize_NUM(Num);
 
 		parse_format(format, str, NUM_keywords,
-					 NULL, NUM_index, NUM_TYPE, Num);
+					 NULL, NUM_index, NUM_FLAG, Num);
 	}
 	else
 	{
-- 
2.14.3

0003-Introduce-RRRR-and-RR-revise-YYY-YY-and-Y-datetime-6.patchapplication/octet-stream; name=0003-Introduce-RRRR-and-RR-revise-YYY-YY-and-Y-datetime-6.patchDownload
From fdfcfecbcf9cbc92696d0f20cac89b9c87e83d88 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Tue, 20 Aug 2019 07:57:11 +0300
Subject: [PATCH 3/8] Introduce RRRR and RR, revise YYY, YY and Y datetime
 format patterns

SQL Standard 2016 defines RR and RRRR format patterns and interprets YYY, YY
and Y format patterns in the different way than current implementation of
to_date()/to_timestamp() does.

According to standard YYY, YY and Y should get higher digits from current year.
Our current implementation gets higher digits so that the result is closest
to 2020.  Now, we get just higher digits from 2020 instead.  We use 2020 instead
of current year in order to keep our conversion immutable.

According to standard RR behavior is implementation-defined and should select
marching 4-digit year in the interval [CY - 100; CY + 100], where CY is current
year.  We use logic, which was previously used in YY, select the matching year
closest to 2020.

The change is made in favor of upcoming jsonpath .datetime() method.  Standard
applies to jsonpath .datetime() method and CAST (... FORMAT ...) SQL clause.
But in order to keep our behavior consistent, we apply this change to
to_date()/to_timestamp() too.

Discussion: https://postgr.es/m/CAPpHfdsZgYEra_PeCLGNoXOWYx6iU-S3wF8aX0ObQUcZU%2B4XTw%40mail.gmail.com
Author: Alexander Korotkov
---
 doc/src/sgml/func.sgml                 | 32 +++++++++++++++---
 src/backend/utils/adt/formatting.c     | 59 ++++++++++++++++++++++++++--------
 src/test/regress/expected/horology.out | 29 +++++++++++++----
 src/test/regress/sql/horology.sql      | 12 +++++--
 4 files changed, 103 insertions(+), 29 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 0075df41c6a..76ebc7bb132 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -6193,7 +6193,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
         <entry>year (4 or more digits) with comma</entry>
        </row>
        <row>
-        <entry><literal>YYYY</literal></entry>
+        <entry><literal>YYYY</literal>, <literal>RRRR</literal></entry>
         <entry>year (4 or more digits)</entry>
        </row>
        <row>
@@ -6224,6 +6224,10 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
         <entry><literal>I</literal></entry>
         <entry>last digit of ISO 8601 week-numbering year</entry>
        </row>
+       <row>
+        <entry><literal>RR</literal></entry>
+        <entry>last 2 digits of rounded year</entry>
+       </row>
        <row>
         <entry><literal>BC</literal>, <literal>bc</literal>,
         <literal>AD</literal> or <literal>ad</literal></entry>
@@ -6544,10 +6548,18 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
      <listitem>
       <para>
        In <function>to_timestamp</function> and <function>to_date</function>,
-       if the year format specification is less than four digits, e.g.
-       <literal>YYY</literal>, and the supplied year is less than four digits,
-       the year will be adjusted to be nearest to the year 2020, e.g.
-       <literal>95</literal> becomes 1995.
+       when using <literal>YYY</literal>, <literal>YY</literal> or
+       <literal>Y</literal> fields, then higher year digits are taken from
+       2020, e.g. <literal>7</literal> becomes 2027.
+      </para>
+     </listitem>
+
+     <listitem>
+      <para>
+       In <function>to_timestamp</function> and <function>to_date</function>,
+       when using <literal>RR</literal> field, the supplied year will be
+       adjusted to be nearest to the year 2020, e.g. <literal>95</literal>
+       becomes 1995.
       </para>
      </listitem>
 
@@ -6678,6 +6690,16 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
       </para>
      </listitem>
 
+     <listitem>
+      <para>
+        <function>to_char(interval)</function> formats <literal>HH</literal> and
+        <literal>HH12</literal> as shown on a 12-hour clock, for example zero hours
+        and 36 hours both output as <literal>12</literal>, while <literal>HH24</literal>
+        outputs the full hour value, which can exceed 23 in
+        an <type>interval</type> value.
+      </para>
+     </listitem>
+
     </itemizedlist>
    </para>
 
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index d3c5619d584..673a2cff192 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -626,6 +626,8 @@ typedef enum
 	DCH_PM,
 	DCH_Q,
 	DCH_RM,
+	DCH_RRRR,
+	DCH_RR,
 	DCH_SSSSS,
 	DCH_SSSS,
 	DCH_SS,
@@ -679,6 +681,8 @@ typedef enum
 	DCH_pm,
 	DCH_q,
 	DCH_rm,
+	DCH_rrrr,
+	DCH_rr,
 	DCH_sssss,
 	DCH_ssss,
 	DCH_ss,
@@ -789,6 +793,8 @@ static const KeyWord DCH_keywords[] = {
 	{"PM", 2, DCH_PM, false, FROM_CHAR_DATE_NONE},
 	{"Q", 1, DCH_Q, true, FROM_CHAR_DATE_NONE}, /* Q */
 	{"RM", 2, DCH_RM, false, FROM_CHAR_DATE_GREGORIAN}, /* R */
+	{"RRRR", 4, DCH_RRRR, true, FROM_CHAR_DATE_GREGORIAN},
+	{"RR", 2, DCH_RR, true, FROM_CHAR_DATE_GREGORIAN},
 	{"SSSSS", 5, DCH_SSSS, true, FROM_CHAR_DATE_NONE},	/* S */
 	{"SSSS", 4, DCH_SSSS, true, FROM_CHAR_DATE_NONE},
 	{"SS", 2, DCH_SS, true, FROM_CHAR_DATE_NONE},
@@ -842,6 +848,8 @@ static const KeyWord DCH_keywords[] = {
 	{"pm", 2, DCH_pm, false, FROM_CHAR_DATE_NONE},
 	{"q", 1, DCH_Q, true, FROM_CHAR_DATE_NONE}, /* q */
 	{"rm", 2, DCH_rm, false, FROM_CHAR_DATE_GREGORIAN}, /* r */
+	{"rrrr", 4, DCH_RRRR, true, FROM_CHAR_DATE_GREGORIAN},
+	{"rr", 2, DCH_RR, true, FROM_CHAR_DATE_GREGORIAN},
 	{"sssss", 5, DCH_SSSS, true, FROM_CHAR_DATE_NONE},	/* s */
 	{"ssss", 4, DCH_SSSS, true, FROM_CHAR_DATE_NONE},
 	{"ss", 2, DCH_SS, true, FROM_CHAR_DATE_NONE},
@@ -1013,7 +1021,8 @@ static void dump_node(FormatNode *node, int max);
 
 static const char *get_th(char *num, int type);
 static char *str_numth(char *dest, char *num, int type);
-static int	adjust_partial_year_to_2020(int year);
+static int	adjust_partial_year_to_2020(int year, int ndigits);
+static int	adjust_partial_round_year_to_2020(int year);
 static int	strspace_len(char *str);
 static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode);
 static void from_char_set_int(int *dest, const int value, const FormatNode *node);
@@ -2159,8 +2168,27 @@ is_next_separator(FormatNode *n)
 }
 
 
+/*
+ * Get lower 'ndigits' from 'year' while take other higher digits from
+ * 2020.
+ */
 static int
-adjust_partial_year_to_2020(int year)
+adjust_partial_year_to_2020(int year, int ndigits)
+{
+	if (ndigits == 1)
+		return year + 2020;
+	else if (ndigits == 2 || ndigits == 3)
+		return year + 2000;
+	else
+		return year;
+}
+
+
+/*
+ * Get closest to 2020 4-digit year, which corresponds to given 2-digits year.
+ */
+static int
+adjust_partial_round_year_to_2020(int year)
 {
 	/*
 	 * Adjust all dates toward 2020; this is effectively what happens when we
@@ -2172,12 +2200,6 @@ adjust_partial_year_to_2020(int year)
 	/* Force 70-99 into the 1900's */
 	else if (year < 100)
 		return year + 1900;
-	/* Force 100-519 into the 2000's */
-	else if (year < 520)
-		return year + 2000;
-	/* Force 520-999 into the 1000's */
-	else if (year < 1000)
-		return year + 1000;
 	else
 		return year;
 }
@@ -2961,6 +2983,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 					str_numth(s, s, S_TH_TYPE(n->suffix));
 				s += strlen(s);
 				break;
+			case DCH_RRRR:
 			case DCH_YYYY:
 			case DCH_IYYY:
 				sprintf(s, "%0*d",
@@ -2991,6 +3014,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 					str_numth(s, s, S_TH_TYPE(n->suffix));
 				s += strlen(s);
 				break;
+			case DCH_RR:
 			case DCH_YY:
 			case DCH_IY:
 				sprintf(s, "%0*d",
@@ -3376,6 +3400,7 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 					SKIP_THth(s, n->suffix);
 				}
 				break;
+			case DCH_RRRR:
 			case DCH_YYYY:
 			case DCH_IYYY:
 				from_char_parse_int(&out->year, &s, n);
@@ -3384,22 +3409,28 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 				break;
 			case DCH_YYY:
 			case DCH_IYY:
-				if (from_char_parse_int(&out->year, &s, n) < 4)
-					out->year = adjust_partial_year_to_2020(out->year);
+				len = from_char_parse_int(&out->year, &s, n);
+				out->year = adjust_partial_year_to_2020(out->year, len);
 				out->yysz = 3;
 				SKIP_THth(s, n->suffix);
 				break;
+			case DCH_RR:
+				from_char_parse_int(&out->year, &s, n);
+				out->year = adjust_partial_round_year_to_2020(out->year);
+				out->yysz = 2;
+				SKIP_THth(s, n->suffix);
+				break;
 			case DCH_YY:
 			case DCH_IY:
-				if (from_char_parse_int(&out->year, &s, n) < 4)
-					out->year = adjust_partial_year_to_2020(out->year);
+				len = from_char_parse_int(&out->year, &s, n);
+				out->year = adjust_partial_year_to_2020(out->year, len);
 				out->yysz = 2;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Y:
 			case DCH_I:
-				if (from_char_parse_int(&out->year, &s, n) < 4)
-					out->year = adjust_partial_year_to_2020(out->year);
+				len = from_char_parse_int(&out->year, &s, n);
+				out->year = adjust_partial_year_to_2020(out->year, len);
 				out->yysz = 1;
 				SKIP_THth(s, n->suffix);
 				break;
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index 6b53876e062..f68882cc173 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -2597,7 +2597,7 @@ SELECT to_timestamp('1,582nd VIII 21', 'Y,YYYth FMRM DD');
 (1 row)
 
 SELECT to_timestamp('15 "text between quote marks" 98 54 45',
-                    E'HH24 "\\"text between quote marks\\"" YY MI SS');
+                    E'HH24 "\\"text between quote marks\\"" RR MI SS');
          to_timestamp         
 ------------------------------
  Thu Jan 01 15:54:45 1998 PST
@@ -2618,19 +2618,22 @@ SELECT to_timestamp('2000January09Sunday', 'YYYYFMMonthDDFMDay');
 SELECT to_timestamp('97/Feb/16', 'YYMonDD');
 ERROR:  invalid value "/Fe" for "Mon"
 DETAIL:  The given value did not match any of the allowed values for this field.
+SELECT to_timestamp('97/Feb/16', 'RRMonDD');
+ERROR:  invalid value "/Fe" for "Mon"
+DETAIL:  The given value did not match any of the allowed values for this field.
 SELECT to_timestamp('97/Feb/16', 'YY:Mon:DD');
          to_timestamp         
 ------------------------------
- Sun Feb 16 00:00:00 1997 PST
+ Sat Feb 16 00:00:00 2097 PST
 (1 row)
 
-SELECT to_timestamp('97/Feb/16', 'FXYY:Mon:DD');
+SELECT to_timestamp('97/Feb/16', 'FXRR:Mon:DD');
          to_timestamp         
 ------------------------------
  Sun Feb 16 00:00:00 1997 PST
 (1 row)
 
-SELECT to_timestamp('97/Feb/16', 'FXYY/Mon/DD');
+SELECT to_timestamp('97/Feb/16', 'FXRR/Mon/DD');
          to_timestamp         
 ------------------------------
  Sun Feb 16 00:00:00 1997 PST
@@ -2642,6 +2645,12 @@ SELECT to_timestamp('19971116', 'YYYYMMDD');
  Sun Nov 16 00:00:00 1997 PST
 (1 row)
 
+SELECT to_timestamp('19971116', 'RRRRMMDD');
+         to_timestamp         
+------------------------------
+ Sun Nov 16 00:00:00 1997 PST
+(1 row)
+
 SELECT to_timestamp('20000-1116', 'YYYY-MMDD');
          to_timestamp          
 -------------------------------
@@ -2663,11 +2672,17 @@ SELECT to_timestamp('1997 BC 11 16', 'YYYY BC MM DD');
 SELECT to_timestamp('9-1116', 'Y-MMDD');
          to_timestamp         
 ------------------------------
- Mon Nov 16 00:00:00 2009 PST
+ Fri Nov 16 00:00:00 2029 PST
 (1 row)
 
 SELECT to_timestamp('95-1116', 'YY-MMDD');
          to_timestamp         
+------------------------------
+ Wed Nov 16 00:00:00 2095 PST
+(1 row)
+
+SELECT to_timestamp('95-1116', 'RR-MMDD');
+         to_timestamp         
 ------------------------------
  Thu Nov 16 00:00:00 1995 PST
 (1 row)
@@ -2675,7 +2690,7 @@ SELECT to_timestamp('95-1116', 'YY-MMDD');
 SELECT to_timestamp('995-1116', 'YYY-MMDD');
          to_timestamp         
 ------------------------------
- Thu Nov 16 00:00:00 1995 PST
+ Mon Nov 16 00:00:00 2995 PST
 (1 row)
 
 SELECT to_timestamp('2005426', 'YYYYWWD');
@@ -2711,7 +2726,7 @@ SELECT to_timestamp('05527', 'IYIWID');
 SELECT to_timestamp('5527', 'IIWID');
          to_timestamp         
 ------------------------------
- Sun Jan 01 00:00:00 2006 PST
+ Sun Dec 28 00:00:00 2025 PST
 (1 row)
 
 SELECT to_timestamp('2005364', 'IYYYIDDD');
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7a9da1e954..e35cb448508 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -344,7 +344,7 @@ SELECT to_timestamp('My birthday-> Year: 1976, Month: May, Day: 16',
 SELECT to_timestamp('1,582nd VIII 21', 'Y,YYYth FMRM DD');
 
 SELECT to_timestamp('15 "text between quote marks" 98 54 45',
-                    E'HH24 "\\"text between quote marks\\"" YY MI SS');
+                    E'HH24 "\\"text between quote marks\\"" RR MI SS');
 
 SELECT to_timestamp('05121445482000', 'MMDDHH24MISSYYYY');
 
@@ -352,14 +352,18 @@ SELECT to_timestamp('2000January09Sunday', 'YYYYFMMonthDDFMDay');
 
 SELECT to_timestamp('97/Feb/16', 'YYMonDD');
 
+SELECT to_timestamp('97/Feb/16', 'RRMonDD');
+
 SELECT to_timestamp('97/Feb/16', 'YY:Mon:DD');
 
-SELECT to_timestamp('97/Feb/16', 'FXYY:Mon:DD');
+SELECT to_timestamp('97/Feb/16', 'FXRR:Mon:DD');
 
-SELECT to_timestamp('97/Feb/16', 'FXYY/Mon/DD');
+SELECT to_timestamp('97/Feb/16', 'FXRR/Mon/DD');
 
 SELECT to_timestamp('19971116', 'YYYYMMDD');
 
+SELECT to_timestamp('19971116', 'RRRRMMDD');
+
 SELECT to_timestamp('20000-1116', 'YYYY-MMDD');
 
 SELECT to_timestamp('1997 AD 11 16', 'YYYY BC MM DD');
@@ -369,6 +373,8 @@ SELECT to_timestamp('9-1116', 'Y-MMDD');
 
 SELECT to_timestamp('95-1116', 'YY-MMDD');
 
+SELECT to_timestamp('95-1116', 'RR-MMDD');
+
 SELECT to_timestamp('995-1116', 'YYY-MMDD');
 
 SELECT to_timestamp('2005426', 'YYYYWWD');
-- 
2.14.3

0005-Implement-parse_datetime-function-6.patchapplication/octet-stream; name=0005-Implement-parse_datetime-function-6.patchDownload
From 0ac394ae02aaa64ce34a2ac9853d0f45bd102d59 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Mon, 26 Aug 2019 05:34:19 +0300
Subject: [PATCH 5/8] Implement parse_datetime() function

This commit adds parse_datetime() function, which implements datetime
parsing with extended features demanded by upcoming jsonpath .datetime()
method:

 * Dynamic type identification based on template string,
 * Support for standard-conforming 'strict' mode,
 * Timezone offset is returned as separate value.

Extracted from original patch by Nikita Glukhov, Teodor Sigaev, Oleg Bartunov.
Revised by me.

Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
Discussion: https://postgr.es/m/CAPpHfdsZgYEra_PeCLGNoXOWYx6iU-S3wF8aX0ObQUcZU%2B4XTw%40mail.gmail.com
Author: Nikita Glukhov, Teodor Sigaev, Oleg Bartunov, Alexander Korotkov
Reviewed-by: Anastasia Lubennikova, Peter Eisentraut
---
 src/backend/utils/adt/date.c       |  11 +-
 src/backend/utils/adt/formatting.c | 293 ++++++++++++++++++++++++++++++++++++-
 src/include/utils/date.h           |   3 +
 src/include/utils/formatting.h     |   3 +
 4 files changed, 298 insertions(+), 12 deletions(-)

diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 4b1afb10f92..9e291b5c7bc 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -41,11 +41,6 @@
 #endif
 
 
-static int	tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
-static int	tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
-static void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
-
-
 /* common code for timetypmodin and timetztypmodin */
 static int32
 anytime_typmodin(bool istz, ArrayType *ta)
@@ -1203,7 +1198,7 @@ time_in(PG_FUNCTION_ARGS)
 /* tm2time()
  * Convert a tm structure to a time data type.
  */
-static int
+int
 tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result)
 {
 	*result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec)
@@ -1379,7 +1374,7 @@ time_scale(PG_FUNCTION_ARGS)
  * have a fundamental tie together but rather a coincidence of
  * implementation. - thomas
  */
-static void
+void
 AdjustTimeForTypmod(TimeADT *time, int32 typmod)
 {
 	static const int64 TimeScales[MAX_TIME_PRECISION + 1] = {
@@ -1957,7 +1952,7 @@ time_part(PG_FUNCTION_ARGS)
 /* tm2timetz()
  * Convert a tm structure to a time data type.
  */
-static int
+int
 tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result)
 {
 	result->time = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) *
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 93f99fc7507..bd6fa1115a6 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -1000,6 +1000,11 @@ typedef struct NUMProc
 			   *L_currency_symbol;
 } NUMProc;
 
+/* Return flags for DCH_from_char() */
+#define DCH_DATED	0x01
+#define DCH_TIMED	0x02
+#define DCH_ZONED	0x04
+
 /* ----------
  * Functions
  * ----------
@@ -1034,7 +1039,8 @@ static int	from_char_parse_int(int *dest, char **src, FormatNode *node);
 static int	seq_search(char *name, const char *const *array, int type, int max, int *len);
 static int	from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
 static void do_to_timestamp(text *date_txt, text *fmt, bool std,
-							struct pg_tm *tm, fsec_t *fsec, int *fprec);
+							struct pg_tm *tm, fsec_t *fsec, int *fprec,
+							uint32 *flags);
 static char *fill_str(char *str, int c, int max);
 static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
 static char *int_to_roman(int number);
@@ -3549,6 +3555,111 @@ DCH_prevent_counter_overflow(void)
 	}
 }
 
+/* Get mask of date/time/zone components present in format nodes. */
+static int
+DCH_datetime_type(FormatNode *node)
+{
+	FormatNode *n;
+	int			flags = 0;
+
+	for (n = node; n->type != NODE_TYPE_END; n++)
+	{
+		if (n->type != NODE_TYPE_ACTION)
+			continue;
+
+		switch (n->key->id)
+		{
+			case DCH_FX:
+				break;
+			case DCH_A_M:
+			case DCH_P_M:
+			case DCH_a_m:
+			case DCH_p_m:
+			case DCH_AM:
+			case DCH_PM:
+			case DCH_am:
+			case DCH_pm:
+			case DCH_HH:
+			case DCH_HH12:
+			case DCH_HH24:
+			case DCH_MI:
+			case DCH_SS:
+			case DCH_MS:		/* millisecond */
+			case DCH_US:		/* microsecond */
+			case DCH_FF1:
+			case DCH_FF2:
+			case DCH_FF3:
+			case DCH_FF4:
+			case DCH_FF5:
+			case DCH_FF6:
+			case DCH_SSSS:
+				flags |= DCH_TIMED;
+				break;
+			case DCH_tz:
+			case DCH_TZ:
+			case DCH_OF:
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("formatting field \"%s\" is only supported in to_char",
+								n->key->name)));
+				flags |= DCH_ZONED;
+				break;
+			case DCH_TZH:
+			case DCH_TZM:
+				flags |= DCH_ZONED;
+				break;
+			case DCH_A_D:
+			case DCH_B_C:
+			case DCH_a_d:
+			case DCH_b_c:
+			case DCH_AD:
+			case DCH_BC:
+			case DCH_ad:
+			case DCH_bc:
+			case DCH_MONTH:
+			case DCH_Month:
+			case DCH_month:
+			case DCH_MON:
+			case DCH_Mon:
+			case DCH_mon:
+			case DCH_MM:
+			case DCH_DAY:
+			case DCH_Day:
+			case DCH_day:
+			case DCH_DY:
+			case DCH_Dy:
+			case DCH_dy:
+			case DCH_DDD:
+			case DCH_IDDD:
+			case DCH_DD:
+			case DCH_D:
+			case DCH_ID:
+			case DCH_WW:
+			case DCH_Q:
+			case DCH_CC:
+			case DCH_Y_YYY:
+			case DCH_YYYY:
+			case DCH_IYYY:
+			case DCH_RRRR:
+			case DCH_YYY:
+			case DCH_IYY:
+			case DCH_YY:
+			case DCH_IY:
+			case DCH_RR:
+			case DCH_Y:
+			case DCH_I:
+			case DCH_RM:
+			case DCH_rm:
+			case DCH_W:
+			case DCH_J:
+				flags |= DCH_DATED;
+				break;
+		}
+	}
+
+	return flags;
+}
+
 /* select a DCHCacheEntry to hold the given format picture */
 static DCHCacheEntry *
 DCH_cache_getnew(const char *str, bool std)
@@ -3840,7 +3951,7 @@ to_timestamp(PG_FUNCTION_ARGS)
 	fsec_t		fsec;
 	int			fprec;
 
-	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL);
 
 	/* Use the specified time zone, if any. */
 	if (tm.tm_zone)
@@ -3879,7 +3990,7 @@ to_date(PG_FUNCTION_ARGS)
 	struct pg_tm tm;
 	fsec_t		fsec;
 
-	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL);
 
 	/* Prevent overflow in Julian-day routines */
 	if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3900,6 +4011,176 @@ to_date(PG_FUNCTION_ARGS)
 	PG_RETURN_DATEADT(result);
 }
 
+/*
+ * Convert the 'date_txt' input to a datetime type using argument 'fmt' as a format string.
+ * The actual data type (returned in 'typid', 'typmod') is determined by
+ * the presence of date/time/zone components in the format string.
+ *
+ * When timezone component is present, the corresponding offset is set to '*tz'.
+ */
+Datum
+parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
+			   int32 *typmod, int *tz)
+{
+	struct pg_tm tm;
+	fsec_t		fsec;
+	int			fprec = 0;
+	uint32		flags;
+
+	do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags);
+
+	*typmod = fprec ? fprec : -1;	/* fractional part precision */
+
+	if (flags & DCH_DATED)
+	{
+		if (flags & DCH_TIMED)
+		{
+			if (flags & DCH_ZONED)
+			{
+				TimestampTz result;
+
+				if (tm.tm_zone)
+				{
+					int			dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
+
+					if (dterr)
+						DateTimeParseError(dterr, text_to_cstring(date_txt), "timestamptz");
+				}
+				else
+				{
+					/*
+					 * Time zone is present in format string, but not in input
+					 * string.  Assuming do_to_timestamp() triggers no error
+					 * this should be possible only in non-strict case.
+					 */
+					Assert(!strict);
+
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							 errmsg("missing time zone in input string for type timestamptz")));
+				}
+
+				if (tm2timestamp(&tm, fsec, tz, &result) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("timestamptz out of range")));
+
+				AdjustTimestampForTypmod(&result, *typmod);
+
+				*typid = TIMESTAMPTZOID;
+				return TimestampTzGetDatum(result);
+			}
+			else
+			{
+				Timestamp	result;
+
+				if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("timestamp out of range")));
+
+				AdjustTimestampForTypmod(&result, *typmod);
+
+				*typid = TIMESTAMPOID;
+				return TimestampGetDatum(result);
+			}
+		}
+		else
+		{
+			if (flags & DCH_ZONED)
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+						 errmsg("datetime format is zoned but not timed")));
+			}
+			else
+			{
+				DateADT		result;
+
+				/* Prevent overflow in Julian-day routines */
+				if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("date out of range: \"%s\"",
+									text_to_cstring(date_txt))));
+
+				result = 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(result))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("date out of range: \"%s\"",
+									text_to_cstring(date_txt))));
+
+				*typid = DATEOID;
+				return DateADTGetDatum(result);
+			}
+		}
+	}
+	else if (flags & DCH_TIMED)
+	{
+		if (flags & DCH_ZONED)
+		{
+			TimeTzADT  *result = palloc(sizeof(TimeTzADT));
+
+			if (tm.tm_zone)
+			{
+				int			dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
+
+				if (dterr)
+					DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz");
+			}
+			else
+			{
+				/*
+				 * Time zone is present in format string, but not in input
+				 * string.  Assuming do_to_timestamp() triggers no error this
+				 * should be possible only in non-strict case.
+				 */
+				Assert(!strict);
+
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+						 errmsg("missing time zone in input string for type timetz")));
+			}
+
+			if (tm2timetz(&tm, fsec, *tz, result) != 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("timetz out of range")));
+
+			AdjustTimeForTypmod(&result->time, *typmod);
+
+			*typid = TIMETZOID;
+			return TimeTzADTPGetDatum(result);
+		}
+		else
+		{
+			TimeADT		result;
+
+			if (tm2time(&tm, fsec, &result) != 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("time out of range")));
+
+			AdjustTimeForTypmod(&result, *typmod);
+
+			*typid = TIMEOID;
+			return TimeADTGetDatum(result);
+		}
+	}
+	else
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+				 errmsg("datetime format is not dated and not timed")));
+	}
+
+	return (Datum) 0;
+}
+
 /*
  * do_to_timestamp: shared code for to_timestamp and to_date
  *
@@ -3915,7 +4196,8 @@ to_date(PG_FUNCTION_ARGS)
  */
 static void
 do_to_timestamp(text *date_txt, text *fmt, bool std,
-				struct pg_tm *tm, fsec_t *fsec, int *fprec)
+				struct pg_tm *tm, fsec_t *fsec, int *fprec,
+				uint32 *flags)
 {
 	FormatNode *format;
 	TmFromChar	tmfc;
@@ -3972,6 +4254,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 
 		pfree(fmt_str);
 
+		if (flags)
+			*flags = DCH_datetime_type(format);
+
 		if (!incache)
 			pfree(format);
 	}
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index bec129aff1c..bd15bfa5bb0 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -76,5 +76,8 @@ extern TimeTzADT *GetSQLCurrentTime(int32 typmod);
 extern TimeADT GetSQLLocalTime(int32 typmod);
 extern int	time2tm(TimeADT time, struct pg_tm *tm, fsec_t *fsec);
 extern int	timetz2tm(TimeTzADT *time, struct pg_tm *tm, fsec_t *fsec, int *tzp);
+extern int	tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
+extern int	tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
+extern void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
 
 #endif							/* DATE_H */
diff --git a/src/include/utils/formatting.h b/src/include/utils/formatting.h
index 0117144779e..beeaf10c332 100644
--- a/src/include/utils/formatting.h
+++ b/src/include/utils/formatting.h
@@ -26,4 +26,7 @@ extern char *asc_tolower(const char *buff, size_t nbytes);
 extern char *asc_toupper(const char *buff, size_t nbytes);
 extern char *asc_initcap(const char *buff, size_t nbytes);
 
+extern Datum parse_datetime(text *date_txt, text *fmt, bool std,
+							Oid *typid, int32 *typmod, int *tz);
+
 #endif
-- 
2.14.3

0008-Implement-jsonpath-.datetime-method-6.patchapplication/octet-stream; name=0008-Implement-jsonpath-.datetime-method-6.patchDownload
From 208f5a44cc88a2e600b5df17e5c255bb78a68241 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Sun, 21 Jul 2019 01:16:38 +0300
Subject: [PATCH 8/8] Implement jsonpath .datetime() method

This commit implements jsonpath .datetime() method as it's specified in
SQL/JSON standard.  There are no-argument and single-argument versions of
this method.  No-argument version selects first of ISO datetime formats
matching input string.  Single-argument version accepts template string as
its argument.

Additionally to .datetime() method itself this commit also implements
comparison ability of resulting date and time values.  There is some difficulty
because exising jsonb_path_*() functions are immutable, while comparison of
timezoned and non-timezoned types involves current timezone.  At first, current
timezone could be changes in session.  Moreover, timezones themselves are not
immutable and could be updated.  This is why we let existing immutable functions
throw errors on such non-immutable comparison.  In the same time this commit
provides jsonb_path_*_tz() functions which are stable and support operations
involving timezones.

Support of .datetime() method was the only blocker prevents T832 from being
marked as supported.  sql_features.txt is updated correspondingly.

Extracted from original patch by Nikita Glukhov, Teodor Sigaev, Oleg Bartunov.
Heavily revised by me.  Comments were adjusted by Liudmila Mantrova.

Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
Discussion: https://postgr.es/m/CAPpHfdsZgYEra_PeCLGNoXOWYx6iU-S3wF8aX0ObQUcZU%2B4XTw%40mail.gmail.com
Author: Alexander Korotkov, Nikita Glukhov, Teodor Sigaev, Oleg Bartunov, Liudmila Mantrova
Reviewed-by: Anastasia Lubennikova, Peter Eisentraut
---
 doc/src/sgml/func.sgml                       | 106 +++++-
 src/backend/catalog/sql_features.txt         |   2 +-
 src/backend/catalog/system_views.sql         |  40 ++
 src/backend/utils/adt/jsonpath.c             |  24 +-
 src/backend/utils/adt/jsonpath_exec.c        | 472 ++++++++++++++++++++++--
 src/backend/utils/adt/jsonpath_gram.y        |  14 +
 src/backend/utils/adt/jsonpath_scan.l        |   1 +
 src/backend/utils/errcodes.txt               |   1 +
 src/include/catalog/pg_proc.dat              |  22 ++
 src/include/utils/jsonpath.h                 |   1 +
 src/test/regress/expected/jsonb_jsonpath.out | 526 +++++++++++++++++++++++++++
 src/test/regress/expected/jsonpath.out       |  12 +
 src/test/regress/sql/jsonb_jsonpath.sql      | 172 +++++++++
 src/test/regress/sql/jsonpath.sql            |   2 +
 14 files changed, 1351 insertions(+), 44 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 76ebc7bb132..428b97c5f42 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -11793,16 +11793,6 @@ table2-mapping
   </para>
 
   <itemizedlist>
-   <listitem>
-    <para>
-     <literal>.datetime()</literal> item method is not implemented yet
-     mainly because immutable <type>jsonpath</type> functions and operators
-     cannot reference session timezone, which is used in some datetime
-     operations.  Datetime support will be added to <type>jsonpath</type>
-     in future versions of <productname>PostgreSQL</productname>.
-    </para>
-   </listitem>
-
    <listitem>
     <para>
      A path expression can be a Boolean predicate, although the SQL/JSON
@@ -12008,6 +11998,20 @@ table2-mapping
         <entry><literal>$.z.abs()</literal></entry>
         <entry><literal>0.3</literal></entry>
        </row>
+       <row>
+        <entry><literal>datetime()</literal></entry>
+        <entry>Date/time value converted from a string</entry>
+        <entry><literal>["2015-8-1", "2015-08-12"]</literal></entry>
+        <entry><literal>$[*] ? (@.datetime() &lt; "2015-08-2". datetime())</literal></entry>
+        <entry><literal>2015-8-1</literal></entry>
+       </row>
+       <row>
+        <entry><literal>datetime(<replaceable>template</replaceable>)</literal></entry>
+        <entry>Date/time value converted from a string using the specified template</entry>
+        <entry><literal>["12:30", "18:40"]</literal></entry>
+        <entry><literal>$[*].datetime("HH24:MI")</literal></entry>
+        <entry><literal>"12:30:00", "18:40:00"</literal></entry>
+       </row>
        <row>
         <entry><literal>keyvalue()</literal></entry>
         <entry>
@@ -12025,6 +12029,26 @@ table2-mapping
      </tgroup>
     </table>
 
+    <note>
+     <para>
+      The result type of <literal>datetime()</literal> and
+      <literal>datetime(<replaceable>template</replaceable>)</literal>
+      methods can be <type>date</type>, <type>timetz</type>, <type>time</type>,
+      <type>timestamptz</type>, or <type>timestamp</type>.
+      Both methods determine the result type dynamically.
+     </para>
+     <para>
+      The <literal>datetime()</literal> method sequentially tries ISO formats
+      for <type>date</type>, <type>timetz</type>, <type>time</type>,
+      <type>timestamptz</type>, and <type>timestamp</type>. It stops on
+      the first matching format and the corresponding data type.
+     </para>
+     <para>
+      The <literal>datetime(<replaceable>template</replaceable>)</literal>
+      method determines the result type by the provided template string.
+     </para>
+    </note>
+
     <table id="functions-sqljson-filter-ex-table">
      <title><type>jsonpath</type> Filter Expression Elements</title>
      <tgroup cols="5">
@@ -12167,6 +12191,15 @@ table2-mapping
       </tbody>
      </tgroup>
     </table>
+
+    <note>
+     <para>
+      When different date/time values are compared, an implicit cast is
+      applied. A <type>date</type> value can be cast to <type>timestamp</type>
+      or <type>timestamptz</type>, <type>timestamp</type> can be cast to
+      <type>timestamptz</type>, and <type>time</type> &mdash; to <type>timetz</type>.
+     </para>
+    </note>
    </sect3>
 
   </sect2>
@@ -12399,7 +12432,7 @@ table2-mapping
    <para>
     The <literal>@?</literal> and <literal>@@</literal> operators suppress
     the following errors: lacking object field or array element, unexpected
-    JSON item type, and numeric errors.
+    JSON item type, datetime and numeric errors.
     This behavior might be helpful while searching over JSON document
     collections of varying structure.
    </para>
@@ -12668,18 +12701,33 @@ table2-mapping
   <indexterm>
    <primary>jsonb_path_exists</primary>
   </indexterm>
+  <indexterm>
+   <primary>jsonb_path_exists_tz</primary>
+  </indexterm>
   <indexterm>
    <primary>jsonb_path_match</primary>
   </indexterm>
+  <indexterm>
+   <primary>jsonb_path_match_tz</primary>
+  </indexterm>
   <indexterm>
    <primary>jsonb_path_query</primary>
   </indexterm>
+  <indexterm>
+   <primary>jsonb_path_query_tz</primary>
+  </indexterm>
   <indexterm>
    <primary>jsonb_path_query_array</primary>
   </indexterm>
+  <indexterm>
+   <primary>jsonb_path_query_array_tz</primary>
+  </indexterm>
   <indexterm>
    <primary>jsonb_path_query_first</primary>
   </indexterm>
+  <indexterm>
+   <primary>jsonb_path_query_first_tz</primary>
+  </indexterm>
 
   <table id="functions-json-processing-table">
     <title>JSON Processing Functions</title>
@@ -13019,6 +13067,9 @@ table2-mapping
          <para><literal>
            jsonb_path_exists(target jsonb, path jsonpath [, vars jsonb [, silent bool]])
          </literal></para>
+         <para><literal>
+           jsonb_path_exists_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>boolean</type></entry>
         <entry>
@@ -13039,6 +13090,9 @@ table2-mapping
          <para><literal>
            jsonb_path_match(target jsonb, path jsonpath [, vars jsonb, silent bool])
          </literal></para>
+         <para><literal>
+           jsonb_path_match_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>boolean</type></entry>
         <entry>
@@ -13060,6 +13114,9 @@ table2-mapping
          <para><literal>
            jsonb_path_query(target jsonb, path jsonpath [, vars jsonb, silent bool])
          </literal></para>
+         <para><literal>
+           jsonb_path_query_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>setof jsonb</type></entry>
         <entry>
@@ -13088,6 +13145,9 @@ table2-mapping
          <para><literal>
            jsonb_path_query_array(target jsonb, path jsonpath [, vars jsonb, silent bool])
          </literal></para>
+         <para><literal>
+           jsonb_path_query_array_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>jsonb</type></entry>
         <entry>
@@ -13108,6 +13168,9 @@ table2-mapping
          <para><literal>
            jsonb_path_query_first(target jsonb, path jsonpath [, vars jsonb, silent bool])
          </literal></para>
+         <para><literal>
+           jsonb_path_query_first_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>jsonb</type></entry>
         <entry>
@@ -13250,11 +13313,8 @@ table2-mapping
 
   <note>
    <para>
-    The <literal>jsonb_path_exists</literal>, <literal>jsonb_path_match</literal>,
-    <literal>jsonb_path_query</literal>, <literal>jsonb_path_query_array</literal>, and
-    <literal>jsonb_path_query_first</literal>
-    functions have optional <literal>vars</literal> and <literal>silent</literal>
-    arguments.
+    The <literal>jsonb_path_*</literal> functions have optional
+    <literal>vars</literal> and <literal>silent</literal> arguments.
    </para>
    <para>
     If the <parameter>vars</parameter> argument is specified, it provides an
@@ -13268,6 +13328,20 @@ table2-mapping
    </para>
   </note>
 
+  <note>
+   <para>
+    Some of the <literal>jsonb_path_*</literal> functions have the
+    <literal>_tz</literal> suffix. These functions have been implemented to
+    support comparison of date/time values that involves implicit
+    timezone-aware casts. Since operations with time zones are not immutable,
+    these functions are qualified as stable. Their counterparts without the
+    suffix do not support such casts, so they are immutable and can be used for
+    such use-cases as expression indexes
+    (see <xref linkend="indexes-expressional"/>). There is no difference
+    between these functions for other <type>jsonpath</type> operations.
+   </para>
+  </note>
+
   <para>
     See also <xref linkend="functions-aggregate"/> for the aggregate
     function <function>json_agg</function> which aggregates record
diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index 059ec02cd03..9c68292a54d 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -544,7 +544,7 @@ T828	JSON_QUERY			NO
 T829	JSON_QUERY: array wrapper options			NO	
 T830	Enforcing unique keys in SQL/JSON constructor functions			NO	
 T831	SQL/JSON path language: strict mode			YES	
-T832	SQL/JSON path language: item method			NO	datetime() not yet implemented
+T832	SQL/JSON path language: item method			YES	
 T833	SQL/JSON path language: multiple subscripts			YES	
 T834	SQL/JSON path language: wildcard member accessor			YES	
 T835	SQL/JSON path language: filter expressions			YES	
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ea4c85e3959..423ae4fdc8a 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1287,6 +1287,46 @@ LANGUAGE INTERNAL
 STRICT IMMUTABLE PARALLEL SAFE
 AS 'jsonb_path_query_first';
 
+CREATE OR REPLACE FUNCTION
+  jsonb_path_exists_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                    silent boolean DEFAULT false)
+RETURNS boolean
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_exists_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_match_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                   silent boolean DEFAULT false)
+RETURNS boolean
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_match_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_query_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                   silent boolean DEFAULT false)
+RETURNS SETOF jsonb
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_query_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_query_array_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                         silent boolean DEFAULT false)
+RETURNS jsonb
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_query_array_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_query_first_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                         silent boolean DEFAULT false)
+RETURNS jsonb
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_query_first_tz';
+
 --
 -- The default permissions for functions mean that anyone can execute them.
 -- A number of functions shouldn't be executable by just anyone, but rather
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 87ae60e490f..65c047acc86 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -337,12 +337,14 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
 		case jpiPlus:
 		case jpiMinus:
 		case jpiExists:
+		case jpiDatetime:
 			{
 				int32		arg = reserveSpaceForItemPointer(buf);
 
-				chld = flattenJsonPathParseItem(buf, item->value.arg,
-												nestingLevel + argNestingLevel,
-												insideArraySubscript);
+				chld = !item->value.arg ? pos :
+					flattenJsonPathParseItem(buf, item->value.arg,
+											 nestingLevel + argNestingLevel,
+											 insideArraySubscript);
 				*(int32 *) (buf->data + arg) = chld - pos;
 			}
 			break;
@@ -692,6 +694,15 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
 		case jpiDouble:
 			appendBinaryStringInfo(buf, ".double()", 9);
 			break;
+		case jpiDatetime:
+			appendBinaryStringInfo(buf, ".datetime(", 10);
+			if (v->content.arg)
+			{
+				jspGetArg(v, &elem);
+				printJsonPathItem(buf, &elem, false, false);
+			}
+			appendStringInfoChar(buf, ')');
+			break;
 		case jpiKeyValue:
 			appendBinaryStringInfo(buf, ".keyvalue()", 11);
 			break;
@@ -754,6 +765,8 @@ jspOperationName(JsonPathItemType type)
 			return "floor";
 		case jpiCeiling:
 			return "ceiling";
+		case jpiDatetime:
+			return "datetime";
 		default:
 			elog(ERROR, "unrecognized jsonpath item type: %d", type);
 			return NULL;
@@ -889,6 +902,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
 		case jpiPlus:
 		case jpiMinus:
 		case jpiFilter:
+		case jpiDatetime:
 			read_int32(v->content.arg, base, pos);
 			break;
 		case jpiIndexArray:
@@ -913,7 +927,8 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a)
 		   v->type == jpiIsUnknown ||
 		   v->type == jpiExists ||
 		   v->type == jpiPlus ||
-		   v->type == jpiMinus);
+		   v->type == jpiMinus ||
+		   v->type == jpiDatetime);
 
 	jspInitByBuffer(a, v->base, v->content.arg);
 }
@@ -961,6 +976,7 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
 			   v->type == jpiFloor ||
 			   v->type == jpiCeiling ||
 			   v->type == jpiDouble ||
+			   v->type == jpiDatetime ||
 			   v->type == jpiKeyValue ||
 			   v->type == jpiStartsWith);
 
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index 21106e1da86..9dcc91da5c0 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -66,6 +66,7 @@
 #include "miscadmin.h"
 #include "regex/regex.h"
 #include "utils/builtins.h"
+#include "utils/datetime.h"
 #include "utils/datum.h"
 #include "utils/formatting.h"
 #include "utils/float.h"
@@ -107,6 +108,7 @@ typedef struct JsonPathExecContext
 										 * ignored */
 	bool		throwErrors;	/* with "false" all suppressible errors are
 								 * suppressed */
+	bool		useTz;
 } JsonPathExecContext;
 
 /* Context for LIKE_REGEX execution. */
@@ -173,7 +175,8 @@ typedef JsonPathBool (*JsonPathPredicateCallback) (JsonPathItem *jsp,
 typedef Numeric (*BinaryArithmFunc) (Numeric num1, Numeric num2, bool *error);
 
 static JsonPathExecResult executeJsonPath(JsonPath *path, Jsonb *vars,
-										  Jsonb *json, bool throwErrors, JsonValueList *result);
+										  Jsonb *json, bool throwErrors,
+										  JsonValueList *result, bool useTz);
 static JsonPathExecResult executeItem(JsonPathExecContext *cxt,
 									  JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
 static JsonPathExecResult executeItemOptUnwrapTarget(JsonPathExecContext *cxt,
@@ -214,6 +217,8 @@ static JsonPathBool executeLikeRegex(JsonPathItem *jsp, JsonbValue *str,
 static JsonPathExecResult executeNumericItemMethod(JsonPathExecContext *cxt,
 												   JsonPathItem *jsp, JsonbValue *jb, bool unwrap, PGFunction func,
 												   JsonValueList *found);
+static JsonPathExecResult executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
+												JsonbValue *jb, JsonValueList *found);
 static JsonPathExecResult executeKeyValueMethod(JsonPathExecContext *cxt,
 												JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
 static JsonPathExecResult appendBoolResult(JsonPathExecContext *cxt,
@@ -225,7 +230,8 @@ static void getJsonPathVariable(JsonPathExecContext *cxt,
 static int	JsonbArraySize(JsonbValue *jb);
 static JsonPathBool executeComparison(JsonPathItem *cmp, JsonbValue *lv,
 									  JsonbValue *rv, void *p);
-static JsonPathBool compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2);
+static JsonPathBool compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2,
+								 bool useTz);
 static int	compareNumeric(Numeric a, Numeric b);
 static JsonbValue *copyJsonbValue(JsonbValue *src);
 static JsonPathExecResult getArrayIndex(JsonPathExecContext *cxt,
@@ -246,6 +252,8 @@ static JsonbValue *JsonbInitBinary(JsonbValue *jbv, Jsonb *jb);
 static int	JsonbType(JsonbValue *jb);
 static JsonbValue *getScalar(JsonbValue *scalar, enum jbvType type);
 static JsonbValue *wrapItemsInArray(const JsonValueList *items);
+static int	compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
+							bool useTz, bool *have_error);
 
 /****************** User interface to JsonPath executor ********************/
 
@@ -261,8 +269,8 @@ static JsonbValue *wrapItemsInArray(const JsonValueList *items);
  *		SQL/JSON.  Regarding jsonb_path_match(), this function doesn't have
  *		an analogy in SQL/JSON, so we define its behavior on our own.
  */
-Datum
-jsonb_path_exists(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_exists_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -276,7 +284,7 @@ jsonb_path_exists(PG_FUNCTION_ARGS)
 		silent = PG_GETARG_BOOL(3);
 	}
 
-	res = executeJsonPath(jp, vars, jb, !silent, NULL);
+	res = executeJsonPath(jp, vars, jb, !silent, NULL, tz);
 
 	PG_FREE_IF_COPY(jb, 0);
 	PG_FREE_IF_COPY(jp, 1);
@@ -287,6 +295,18 @@ jsonb_path_exists(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(res == jperOk);
 }
 
+Datum
+jsonb_path_exists(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_exists_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_exists_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_exists_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_exists_opr
  *		Implementation of operator "jsonb @? jsonpath" (2-argument version of
@@ -296,7 +316,7 @@ Datum
 jsonb_path_exists_opr(PG_FUNCTION_ARGS)
 {
 	/* just call the other one -- it can handle both cases */
-	return jsonb_path_exists(fcinfo);
+	return jsonb_path_exists_internal(fcinfo, false);
 }
 
 /*
@@ -304,8 +324,8 @@ jsonb_path_exists_opr(PG_FUNCTION_ARGS)
  *		Returns jsonpath predicate result item for the specified jsonb value.
  *		See jsonb_path_exists() comment for details regarding error handling.
  */
-Datum
-jsonb_path_match(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_match_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -319,7 +339,7 @@ jsonb_path_match(PG_FUNCTION_ARGS)
 		silent = PG_GETARG_BOOL(3);
 	}
 
-	(void) executeJsonPath(jp, vars, jb, !silent, &found);
+	(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 	PG_FREE_IF_COPY(jb, 0);
 	PG_FREE_IF_COPY(jp, 1);
@@ -343,6 +363,18 @@ jsonb_path_match(PG_FUNCTION_ARGS)
 	PG_RETURN_NULL();
 }
 
+Datum
+jsonb_path_match(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_match_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_match_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_match_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_match_opr
  *		Implementation of operator "jsonb @@ jsonpath" (2-argument version of
@@ -352,7 +384,7 @@ Datum
 jsonb_path_match_opr(PG_FUNCTION_ARGS)
 {
 	/* just call the other one -- it can handle both cases */
-	return jsonb_path_match(fcinfo);
+	return jsonb_path_match_internal(fcinfo, false);
 }
 
 /*
@@ -360,8 +392,8 @@ jsonb_path_match_opr(PG_FUNCTION_ARGS)
  *		Executes jsonpath for given jsonb document and returns result as
  *		rowset.
  */
-Datum
-jsonb_path_query(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_query_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	FuncCallContext *funcctx;
 	List	   *found;
@@ -385,7 +417,7 @@ jsonb_path_query(PG_FUNCTION_ARGS)
 		vars = PG_GETARG_JSONB_P_COPY(2);
 		silent = PG_GETARG_BOOL(3);
 
-		(void) executeJsonPath(jp, vars, jb, !silent, &found);
+		(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 		funcctx->user_fctx = JsonValueListGetList(&found);
 
@@ -406,13 +438,25 @@ jsonb_path_query(PG_FUNCTION_ARGS)
 	SRF_RETURN_NEXT(funcctx, JsonbPGetDatum(JsonbValueToJsonb(v)));
 }
 
+Datum
+jsonb_path_query(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_query_array
  *		Executes jsonpath for given jsonb document and returns result as
  *		jsonb array.
  */
-Datum
-jsonb_path_query_array(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_query_array_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -420,18 +464,30 @@ jsonb_path_query_array(PG_FUNCTION_ARGS)
 	Jsonb	   *vars = PG_GETARG_JSONB_P(2);
 	bool		silent = PG_GETARG_BOOL(3);
 
-	(void) executeJsonPath(jp, vars, jb, !silent, &found);
+	(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 	PG_RETURN_JSONB_P(JsonbValueToJsonb(wrapItemsInArray(&found)));
 }
 
+Datum
+jsonb_path_query_array(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_array_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_array_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_array_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_query_first
  *		Executes jsonpath for given jsonb document and returns first result
  *		item.  If there are no items, NULL returned.
  */
-Datum
-jsonb_path_query_first(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_query_first_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -439,7 +495,7 @@ jsonb_path_query_first(PG_FUNCTION_ARGS)
 	Jsonb	   *vars = PG_GETARG_JSONB_P(2);
 	bool		silent = PG_GETARG_BOOL(3);
 
-	(void) executeJsonPath(jp, vars, jb, !silent, &found);
+	(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 	if (JsonValueListLength(&found) >= 1)
 		PG_RETURN_JSONB_P(JsonbValueToJsonb(JsonValueListHead(&found)));
@@ -447,6 +503,18 @@ jsonb_path_query_first(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 }
 
+Datum
+jsonb_path_query_first(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_first_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_first_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_first_internal(fcinfo, true);
+}
+
 /********************Execute functions for JsonPath**************************/
 
 /*
@@ -470,7 +538,7 @@ jsonb_path_query_first(PG_FUNCTION_ARGS)
  */
 static JsonPathExecResult
 executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors,
-				JsonValueList *result)
+				JsonValueList *result, bool useTz)
 {
 	JsonPathExecContext cxt;
 	JsonPathExecResult res;
@@ -500,6 +568,7 @@ executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors,
 	cxt.lastGeneratedObjectId = vars ? 2 : 1;
 	cxt.innermostArraySize = -1;
 	cxt.throwErrors = throwErrors;
+	cxt.useTz = useTz;
 
 	if (jspStrictAbsenseOfErrors(&cxt) && !result)
 	{
@@ -1028,6 +1097,12 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
 			}
 			break;
 
+		case jpiDatetime:
+			if (unwrap && JsonbType(jb) == jbvArray)
+				return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
+
+			return executeDateTimeMethod(cxt, jsp, jb, found);
+
 		case jpiKeyValue:
 			if (unwrap && JsonbType(jb) == jbvArray)
 				return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
@@ -1214,7 +1289,7 @@ executeBoolItem(JsonPathExecContext *cxt, JsonPathItem *jsp,
 			jspGetLeftArg(jsp, &larg);
 			jspGetRightArg(jsp, &rarg);
 			return executePredicate(cxt, jsp, &larg, &rarg, jb, true,
-									executeComparison, NULL);
+									executeComparison, cxt);
 
 		case jpiStartsWith:		/* 'whole STARTS WITH initial' */
 			jspGetLeftArg(jsp, &larg);	/* 'whole' */
@@ -1717,6 +1792,138 @@ executeNumericItemMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
 	return executeNextItem(cxt, jsp, &next, jb, found, false);
 }
 
+/*
+ * Implementation of the .datetime() method.
+ *
+ * Converts a string into a date/time value. The actual type is determined at run time.
+ * If an argument is provided, this argument is used as a template string.
+ * Otherwise, the first fitting ISO format is selected.
+ */
+static JsonPathExecResult
+executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
+					  JsonbValue *jb, JsonValueList *found)
+{
+	JsonbValue	jbvbuf;
+	Datum		value;
+	text	   *datetime;
+	Oid			typid;
+	int32		typmod = -1;
+	int			tz = 0;
+	bool		hasNext;
+	JsonPathExecResult res = jperNotFound;
+	JsonPathItem elem;
+
+	if (!(jb = getScalar(jb, jbvString)))
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+							  errmsg("invalid argument for SQL/JSON datetime function"),
+							  errdetail("jsonpath item method .%s() can only be applied to a string",
+										jspOperationName(jsp->type)))));
+
+	datetime = cstring_to_text_with_len(jb->val.string.val,
+										jb->val.string.len);
+
+	if (jsp->content.arg)
+	{
+		text	   *template;
+		char	   *template_str;
+		int			template_len;
+		bool		have_error = false;
+
+		jspGetArg(jsp, &elem);
+
+		if (elem.type != jpiString)
+			elog(ERROR, "invalid jsonpath item type for .datetime() argument");
+
+		template_str = jspGetString(&elem, &template_len);
+
+		template = cstring_to_text_with_len(template_str,
+											template_len);
+
+		value = parse_datetime(datetime, template, true,
+							   &typid, &typmod, &tz,
+							   jspThrowErrors(cxt) ? NULL : &have_error);
+
+		if (have_error)
+			res = jperError;
+		else
+			res = jperOk;
+	}
+	else
+	{
+		/*
+		 * According to SQL/JSON standard enumerate ISO formats for: date,
+		 * timetz, time, timestamptz, timestamp.
+		 */
+		static const char *fmt_str[] =
+		{
+			"yyyy-mm-dd",
+			"HH24:MI:SS TZH:TZM",
+			"HH24:MI:SS TZH",
+			"HH24:MI:SS",
+			"yyyy-mm-dd HH24:MI:SS TZH:TZM",
+			"yyyy-mm-dd HH24:MI:SS TZH",
+			"yyyy-mm-dd HH24:MI:SS"
+		};
+
+		/* cache for format texts */
+		static text *fmt_txt[lengthof(fmt_str)] = {0};
+		int			i;
+
+		/* loop until datetime format fits */
+		for (i = 0; i < lengthof(fmt_str); i++)
+		{
+			bool		have_error = false;
+
+			if (!fmt_txt[i])
+			{
+				MemoryContext oldcxt =
+				MemoryContextSwitchTo(TopMemoryContext);
+
+				fmt_txt[i] = cstring_to_text(fmt_str[i]);
+				MemoryContextSwitchTo(oldcxt);
+			}
+
+			value = parse_datetime(datetime, fmt_txt[i], true,
+								   &typid, &typmod, &tz,
+								   &have_error);
+
+			if (!have_error)
+			{
+				res = jperOk;
+				break;
+			}
+		}
+
+		if (res == jperNotFound)
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+								  errmsg("invalid argument for SQL/JSON datetime function"),
+								  errdetail("unrecognized datetime format"),
+								  errhint("use datetime template argument for explicit format specification"))));
+	}
+
+	pfree(datetime);
+
+	if (jperIsError(res))
+		return res;
+
+	hasNext = jspGetNext(jsp, &elem);
+
+	if (!hasNext && !found)
+		return res;
+
+	jb = hasNext ? &jbvbuf : palloc(sizeof(*jb));
+
+	jb->type = jbvDatetime;
+	jb->val.datetime.value = value;
+	jb->val.datetime.typid = typid;
+	jb->val.datetime.typmod = typmod;
+	jb->val.datetime.tz = tz;
+
+	return executeNextItem(cxt, jsp, &elem, jb, found, hasNext);
+}
+
 /*
  * Implementation of .keyvalue() method.
  *
@@ -1977,7 +2184,9 @@ JsonbArraySize(JsonbValue *jb)
 static JsonPathBool
 executeComparison(JsonPathItem *cmp, JsonbValue *lv, JsonbValue *rv, void *p)
 {
-	return compareItems(cmp->type, lv, rv);
+	JsonPathExecContext *cxt = (JsonPathExecContext *) p;
+
+	return compareItems(cmp->type, lv, rv, cxt->useTz);
 }
 
 /*
@@ -2072,7 +2281,7 @@ compareStrings(const char *mbstr1, int mblen1,
  * Compare two SQL/JSON items using comparison operation 'op'.
  */
 static JsonPathBool
-compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2)
+compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2, bool useTz)
 {
 	int			cmp;
 	bool		res;
@@ -2113,6 +2322,21 @@ compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2)
 			cmp = compareStrings(jb1->val.string.val, jb1->val.string.len,
 								 jb2->val.string.val, jb2->val.string.len);
 			break;
+		case jbvDatetime:
+			{
+				bool		have_error = false;
+
+				cmp = compareDatetime(jb1->val.datetime.value,
+									  jb1->val.datetime.typid,
+									  jb2->val.datetime.value,
+									  jb2->val.datetime.typid,
+									  useTz,
+									  &have_error);
+
+				if (have_error)
+					return jpbUnknown;
+			}
+			break;
 
 		case jbvBinary:
 		case jbvArray:
@@ -2372,3 +2596,205 @@ wrapItemsInArray(const JsonValueList *items)
 
 	return pushJsonbValue(&ps, WJB_END_ARRAY, NULL);
 }
+
+/*
+ * Cross-type comparison of two datetime SQL/JSON items.  If items are
+ * uncomparable, 'error' flag is set.
+ */
+static int
+compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
+				bool useTz, bool *have_error)
+{
+	PGFunction cmpfunc = NULL;
+
+	switch (typid1)
+	{
+		case DATEOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					cmpfunc = date_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					val1 = date2timestamp_opt_error(val1, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"date", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val1 = date2timestamptz_opt_error(val1, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMEOID:
+			switch (typid2)
+			{
+				case TIMEOID:
+					cmpfunc = time_cmp;
+
+					break;
+
+				case TIMETZOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"time", "timetz"),
+								 errhint("use *_tz() function for timezone support")));
+					val1 = DirectFunctionCall1(time_timetz, val1);
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case DATEOID:
+				case TIMESTAMPOID:
+				case TIMESTAMPTZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMETZOID:
+			switch (typid2)
+			{
+				case TIMEOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"time", "timetz"),
+								 errhint("use *_tz() function for timezone support")));
+					val2 = DirectFunctionCall1(time_timetz, val2);
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case TIMETZOID:
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case DATEOID:
+				case TIMESTAMPOID:
+				case TIMESTAMPTZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMESTAMPOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					val2 = date2timestamp_opt_error(val2, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"timestamp", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val1 = timestamp2timestamptz_opt_error(val1, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMESTAMPTZOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"date", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val2 = date2timestamptz_opt_error(val2, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"timestamp", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val2 = timestamp2timestamptz_opt_error(val2, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		default:
+			elog(ERROR, "unrecognized SQL/JSON datetime type oid: %d",
+				 typid1);
+	}
+
+	if (*have_error)
+		return 0;
+
+	if (!cmpfunc)
+		elog(ERROR, "unrecognized SQL/JSON datetime type oid: %d",
+			 typid2);
+
+	*have_error = false;
+
+	return DatumGetInt32(DirectFunctionCall2(cmpfunc, val1, val2));
+}
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
index 91b4b2f5985..059faeeb2c7 100644
--- a/src/backend/utils/adt/jsonpath_gram.y
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -94,12 +94,14 @@ static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr,
 %token	<str>		LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
 %token	<str>		ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P
 %token	<str>		ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P KEYVALUE_P
+%token	<str>		DATETIME_P
 
 %type	<result>	result
 
 %type	<value>		scalar_value path_primary expr array_accessor
 					any_path accessor_op key predicate delimited_predicate
 					index_elem starts_with_initial expr_or_predicate
+					datetime_template opt_datetime_template
 
 %type	<elems>		accessor_expr
 
@@ -247,9 +249,20 @@ accessor_op:
 	| array_accessor				{ $$ = $1; }
 	| '.' any_path					{ $$ = $2; }
 	| '.' method '(' ')'			{ $$ = makeItemType($2); }
+	| '.' DATETIME_P '(' opt_datetime_template ')'
+									{ $$ = makeItemUnary(jpiDatetime, $4); }
 	| '?' '(' predicate ')'			{ $$ = makeItemUnary(jpiFilter, $3); }
 	;
 
+datetime_template:
+	STRING_P						{ $$ = makeItemString(&$1); }
+	;
+
+opt_datetime_template:
+	datetime_template				{ $$ = $1; }
+	| /* EMPTY */					{ $$ = NULL; }
+	;
+
 key:
 	key_name						{ $$ = makeItemKey(&$1); }
 	;
@@ -272,6 +285,7 @@ key_name:
 	| FLOOR_P
 	| DOUBLE_P
 	| CEILING_P
+	| DATETIME_P
 	| KEYVALUE_P
 	| LAST_P
 	| STARTS_P
diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l
index 2165ffcc254..ced65edb59b 100644
--- a/src/backend/utils/adt/jsonpath_scan.l
+++ b/src/backend/utils/adt/jsonpath_scan.l
@@ -340,6 +340,7 @@ static const JsonPathKeyword keywords[] = {
 	{ 6, false,	STRICT_P,	"strict"},
 	{ 7, false,	CEILING_P,	"ceiling"},
 	{ 7, false,	UNKNOWN_P,	"unknown"},
+	{ 8, false,	DATETIME_P,	"datetime"},
 	{ 8, false,	KEYVALUE_P,	"keyvalue"},
 	{ 10,false, LIKE_REGEX_P, "like_regex"},
 };
diff --git a/src/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index 867e98b690b..2bd283085e3 100644
--- a/src/backend/utils/errcodes.txt
+++ b/src/backend/utils/errcodes.txt
@@ -207,6 +207,7 @@ Section: Class 22 - Data Exception
 2200S    E    ERRCODE_INVALID_XML_COMMENT                                    invalid_xml_comment
 2200T    E    ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION                     invalid_xml_processing_instruction
 22030    E    ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE                        duplicate_json_object_key_value
+22031    E    ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION            invalid_argument_for_json_datetime_function
 22032    E    ERRCODE_INVALID_JSON_TEXT                                      invalid_json_text
 22033    E    ERRCODE_INVALID_SQL_JSON_SUBSCRIPT                             invalid_sql_json_subscript
 22034    E    ERRCODE_MORE_THAN_ONE_SQL_JSON_ITEM                            more_than_one_sql_json_item
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index e6645f139ce..23d3795688c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9332,6 +9332,28 @@
   proname => 'jsonb_path_match', prorettype => 'bool',
   proargtypes => 'jsonb jsonpath jsonb bool', prosrc => 'jsonb_path_match' },
 
+{ oid => '6015', descr => 'jsonpath exists test with timezone',
+  proname => 'jsonb_path_exists_tz', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_exists_tz' },
+{ oid => '6016', descr => 'jsonpath query with timezone',
+  proname => 'jsonb_path_query_tz', provolatile => 's',
+  prorows => '1000', proretset => 't',
+  prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_query_tz' },
+{ oid => '6017', descr => 'jsonpath query wrapped into array with timezone',
+  proname => 'jsonb_path_query_array_tz', provolatile => 's',
+  prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_query_array_tz' },
+{ oid => '6018', descr => 'jsonpath query first item with timezone',
+  proname => 'jsonb_path_query_first_tz', provolatile => 's',
+  prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_query_first_tz' },
+{ oid => '6019', descr => 'jsonpath match with timezone',
+  proname => 'jsonb_path_match_tz', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_match_tz' },
+
 { oid => '4010', descr => 'implementation of @? operator',
   proname => 'jsonb_path_exists_opr', prorettype => 'bool',
   proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_path_exists_opr' },
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 40ad5fda928..f6b17c8aa2f 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -79,6 +79,7 @@ typedef enum JsonPathItemType
 	jpiFloor,					/* .floor() item method */
 	jpiCeiling,					/* .ceiling() item method */
 	jpiDouble,					/* .double() item method */
+	jpiDatetime,				/* .datetime() item method */
 	jpiKeyValue,				/* .keyvalue() item method */
 	jpiSubscript,				/* array subscript: 'expr' or 'expr TO expr' */
 	jpiLast,					/* LAST array subscript */
diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
index 0202667a1f7..f40a97dc4e1 100644
--- a/src/test/regress/expected/jsonb_jsonpath.out
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -1658,6 +1658,532 @@ select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ lik
  "a\b"
 (1 row)
 
+select jsonb_path_query('null', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('true', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('1', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('[]', '$.datetime()');
+ jsonb_path_query 
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'strict $.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('{}', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('""', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  unrecognized datetime format
+HINT:  use datetime template argument for explicit format specification
+select jsonb_path_query('"12:34"', '$.datetime("aaa")');
+ERROR:  invalid datetime format separator: "a"
+select jsonb_path_query('"aaaa"', '$.datetime("HH24")');
+ERROR:  invalid value "aa" for "HH24"
+DETAIL:  Value must be an integer.
+select jsonb '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")';
+ ?column? 
+----------
+ t
+(1 row)
+
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+ jsonb_path_query 
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+ jsonb_path_query 
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+ERROR:  trailing characters remain in input string after datetime format
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+ERROR:  trailing characters remain in input string after datetime format
+select jsonb_path_query('"10-03-2017 12:34"', '       $.datetime("dd-mm-yyyy HH24:MI").type()');
+       jsonb_path_query        
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+      jsonb_path_query      
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+     jsonb_path_query     
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+   jsonb_path_query    
+-----------------------
+ "time with time zone"
+(1 row)
+
+set time zone '+00';
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+ jsonb_path_query 
+------------------
+ "12:34:00"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone '+10';
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+ jsonb_path_query 
+------------------
+ "12:34:00"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone default;
+select jsonb_path_query('"2017-03-10"', '$.datetime().type()');
+ jsonb_path_query 
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"2017-03-10"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+       jsonb_path_query        
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:34:56"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()');
+      jsonb_path_query      
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:56+03:00"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()');
+      jsonb_path_query      
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:56+03:10"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime().type()');
+     jsonb_path_query     
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "12:34:56"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()');
+   jsonb_path_query    
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "12:34:56+03:00"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()');
+   jsonb_path_query    
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "12:34:56+03:10"
+(1 row)
+
+set time zone '+00';
+-- date comparison
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR:  cannot convert value from date to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR:  cannot convert value from date to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR:  cannot convert value from date to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10"
+ "2017-03-10T00:00:00"
+ "2017-03-10T03:00:00+03:00"
+(3 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10"
+ "2017-03-11"
+ "2017-03-10T00:00:00"
+ "2017-03-10T12:34:56"
+ "2017-03-10T03:00:00+03:00"
+(5 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-09"
+ "2017-03-10T01:02:03+04:00"
+(2 rows)
+
+-- time comparison
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00"
+ "12:35:00+00:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00"
+ "12:36:00"
+ "12:35:00+00:00"
+(3 rows)
+
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:34:00"
+ "12:35:00+01:00"
+ "13:35:00+01:00"
+(3 rows)
+
+-- timetz comparison
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00+01:00"
+(1 row)
+
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00+01:00"
+ "12:36:00+01:00"
+ "12:35:00-02:00"
+ "11:35:00"
+ "12:35:00"
+(5 rows)
+
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:34:00+01:00"
+ "12:35:00+02:00"
+ "10:35:00"
+(3 rows)
+
+-- timestamp comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T13:35:00+01:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T12:36:00"
+ "2017-03-10T13:35:00+01:00"
+ "2017-03-10T12:35:00-01:00"
+ "2017-03-11"
+(5 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:34:00"
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10"
+(3 rows)
+
+-- timestamptz comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T11:35:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+set time zone default;
 -- jsonpath operators
 SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
  jsonb_path_query 
diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
index ecdd453942b..f5d5fa4fb8d 100644
--- a/src/test/regress/expected/jsonpath.out
+++ b/src/test/regress/expected/jsonpath.out
@@ -401,6 +401,18 @@ select '$.keyvalue().key'::jsonpath;
  $.keyvalue()."key"
 (1 row)
 
+select '$.datetime()'::jsonpath;
+   jsonpath   
+--------------
+ $.datetime()
+(1 row)
+
+select '$.datetime("datetime template")'::jsonpath;
+            jsonpath             
+---------------------------------
+ $.datetime("datetime template")
+(1 row)
+
 select '$ ? (@ starts with "abc")'::jsonpath;
         jsonpath         
 -------------------------
diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
index e7629fb7f9d..a6cb74798a6 100644
--- a/src/test/regress/sql/jsonb_jsonpath.sql
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -346,6 +346,178 @@ select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ lik
 select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\B$" flag "iq")');
 select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\b$" flag "")');
 
+select jsonb_path_query('null', '$.datetime()');
+select jsonb_path_query('true', '$.datetime()');
+select jsonb_path_query('1', '$.datetime()');
+select jsonb_path_query('[]', '$.datetime()');
+select jsonb_path_query('[]', 'strict $.datetime()');
+select jsonb_path_query('{}', '$.datetime()');
+select jsonb_path_query('""', '$.datetime()');
+select jsonb_path_query('"12:34"', '$.datetime("aaa")');
+select jsonb_path_query('"aaaa"', '$.datetime("HH24")');
+
+select jsonb '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")';
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+
+select jsonb_path_query('"10-03-2017 12:34"', '       $.datetime("dd-mm-yyyy HH24:MI").type()');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+
+set time zone '+00';
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone '+10';
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone default;
+
+select jsonb_path_query('"2017-03-10"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()');
+select jsonb_path_query('"12:34:56"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56"', '$.datetime()');
+select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56 +3"', '$.datetime()');
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()');
+
+set time zone '+00';
+
+-- date comparison
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+
+-- time comparison
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+
+-- timetz comparison
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+
+-- timestamp comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+
+-- timestamptz comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+
+set time zone default;
+
 -- jsonpath operators
 
 SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
index 29ea77a4858..713d32400a7 100644
--- a/src/test/regress/sql/jsonpath.sql
+++ b/src/test/regress/sql/jsonpath.sql
@@ -72,6 +72,8 @@ select '"aaa".type()'::jsonpath;
 select 'true.type()'::jsonpath;
 select '$.double().floor().ceiling().abs()'::jsonpath;
 select '$.keyvalue().key'::jsonpath;
+select '$.datetime()'::jsonpath;
+select '$.datetime("datetime template")'::jsonpath;
 
 select '$ ? (@ starts with "abc")'::jsonpath;
 select '$ ? (@ starts with $var)'::jsonpath;
-- 
2.14.3

0006-Error-suppression-support-for-upcoming-jsonpath-.d-6.patchapplication/octet-stream; name=0006-Error-suppression-support-for-upcoming-jsonpath-.d-6.patchDownload
From a2f7609d97e12db9d5f3d8249e9fc2f480e7f092 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Sun, 21 Jul 2019 01:14:48 +0300
Subject: [PATCH 6/8] Error suppression support for upcoming jsonpath
 .datetime() method

Add support of error suppression in some date and time manipulation functions
as it's required for jsonpath .datetime() method support.  This commit doesn't
use PG_TRY()/PG_CATCH() in order to implement that.  Instead, it provides
internal versions of date and time functions used, which support error
suppression.

Discussion: https://postgr.es/m/CAPpHfdsZgYEra_PeCLGNoXOWYx6iU-S3wF8aX0ObQUcZU%2B4XTw%40mail.gmail.com
Author: Alexander Korotkov, Nikita Glukhov
Reviewed-by: Anastasia Lubennikova, Peter Eisentraut
---
 src/backend/utils/adt/date.c       |  86 ++++--
 src/backend/utils/adt/formatting.c | 537 ++++++++++++++++++++++++-------------
 src/backend/utils/adt/timestamp.c  |  66 +++--
 src/include/utils/date.h           |   2 +
 src/include/utils/datetime.h       |   2 +
 src/include/utils/formatting.h     |   3 +-
 src/include/utils/timestamp.h      |   3 +
 7 files changed, 478 insertions(+), 221 deletions(-)

diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 9e291b5c7bc..fa50d79c05d 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -550,13 +550,15 @@ date_mii(PG_FUNCTION_ARGS)
 	PG_RETURN_DATEADT(result);
 }
 
+
 /*
- * Internal routines for promoting date to timestamp and timestamp with
- * time zone
+ * Promote date to timestamp.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero is returned.
  */
-
-static Timestamp
-date2timestamp(DateADT dateVal)
+Timestamp
+date2timestamp_opt_error(DateADT dateVal, bool *have_error)
 {
 	Timestamp	result;
 
@@ -572,9 +574,19 @@ date2timestamp(DateADT dateVal)
 		 * boundary need be checked for overflow.
 		 */
 		if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("date out of range for timestamp")));
+		{
+			if (have_error)
+			{
+				*have_error = true;
+				return (Timestamp) 0;
+			}
+			else
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("date out of range for timestamp")));
+			}
+		}
 
 		/* date is days since 2000, timestamp is microseconds since same... */
 		result = dateVal * USECS_PER_DAY;
@@ -583,8 +595,23 @@ date2timestamp(DateADT dateVal)
 	return result;
 }
 
+/*
+ * Single-argument version of date2timestamp_opt_error().
+ */
 static TimestampTz
-date2timestamptz(DateADT dateVal)
+date2timestamp(DateADT dateVal)
+{
+	return date2timestamp_opt_error(dateVal, NULL);
+}
+
+/*
+ * Promote date to timestamp with time zone.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero is returned.
+ */
+TimestampTz
+date2timestamptz_opt_error(DateADT dateVal, bool *have_error)
 {
 	TimestampTz result;
 	struct pg_tm tt,
@@ -603,9 +630,19 @@ date2timestamptz(DateADT dateVal)
 		 * boundary need be checked for overflow.
 		 */
 		if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("date out of range for timestamp")));
+		{
+			if (have_error)
+			{
+				*have_error = true;
+				return (TimestampTz) 0;
+			}
+			else
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("date out of range for timestamp")));
+			}
+		}
 
 		j2date(dateVal + POSTGRES_EPOCH_JDATE,
 			   &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday));
@@ -621,14 +658,33 @@ date2timestamptz(DateADT dateVal)
 		 * of time zone, check for allowed timestamp range after adding tz.
 		 */
 		if (!IS_VALID_TIMESTAMP(result))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("date out of range for timestamp")));
+		{
+			if (have_error)
+			{
+				*have_error = true;
+				return (TimestampTz) 0;
+			}
+			else
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("date out of range for timestamp")));
+			}
+		}
 	}
 
 	return result;
 }
 
+/*
+ * Single-argument version of date2timestamptz_opt_error().
+ */
+static TimestampTz
+date2timestamptz(DateADT dateVal)
+{
+	return date2timestamptz_opt_error(dateVal, NULL);
+}
+
 /*
  * date2timestamp_no_overflow
  *
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index bd6fa1115a6..0fd8ba32790 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -98,6 +98,43 @@
 #include "utils/numeric.h"
 #include "utils/pg_locale.h"
 
+/* ----------
+ * Convenience macros for error handling
+ * ----------
+ *
+ * Two macros below help to handle errors in functions that take
+ * 'bool *have_error' argument.  When this argument is not NULL, it's expected
+ * that function will suppress ereports when possible.  Instead it should
+ * return some default value and set *have_error flag.
+ *
+ * RETURN_ERROR() macro intended to wrap ereport() calls.  When have_error
+ * function argument is not NULL, then instead of ereport'ing we set
+ * *have_error flag and go to on_error label.  It's supposed that jump
+ * resources will be freed and some 'default' value returned.
+ *
+ * CHECK_ERROR() jumps on_error label when *have_error flag is defined and set.
+ * It's supposed to be used for immediate exit from the function on error
+ * after call of another function with 'bool *have_error' argument.
+ */
+#define RETURN_ERROR(throw_error) \
+do { \
+	if (have_error) \
+	{ \
+		*have_error = true; \
+		goto on_error; \
+	} \
+	else \
+	{ \
+		throw_error; \
+	} \
+} while (0)
+
+#define CHECK_ERROR \
+do { \
+	if (have_error && *have_error) \
+		goto on_error; \
+} while (0)
+
 /* ----------
  * Routines flags
  * ----------
@@ -1020,7 +1057,7 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 static void DCH_to_char(FormatNode *node, bool is_interval,
 						TmToChar *in, char *out, Oid collid);
 static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
-						  bool std);
+						  bool std, bool *have_error);
 
 #ifdef DEBUG_TO_FROM_CHAR
 static void dump_index(const KeyWord *k, const int *index);
@@ -1032,15 +1069,21 @@ static char *str_numth(char *dest, char *num, int type);
 static int	adjust_partial_year_to_2020(int year, int ndigits);
 static int	adjust_partial_round_year_to_2020(int year);
 static int	strspace_len(char *str);
-static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode);
-static void from_char_set_int(int *dest, const int value, const FormatNode *node);
-static int	from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node);
-static int	from_char_parse_int(int *dest, char **src, FormatNode *node);
+static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode,
+							   bool *have_error);
+static void from_char_set_int(int *dest, const int value, const FormatNode *node,
+							  bool *have_error);
+static int	from_char_parse_int_len(int *dest, char **src, const int len,
+									FormatNode *node, bool *have_error);
+static int	from_char_parse_int(int *dest, char **src, FormatNode *node,
+								bool *have_error);
 static int	seq_search(char *name, const char *const *array, int type, int max, int *len);
-static int	from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
+static int	from_char_seq_search(int *dest, char **src,
+								 const char *const *array, int type, int max,
+								 FormatNode *node, bool *have_error);
 static void do_to_timestamp(text *date_txt, text *fmt, bool std,
 							struct pg_tm *tm, fsec_t *fsec, int *fprec,
-							uint32 *flags);
+							uint32 *flags, bool *have_error);
 static char *fill_str(char *str, int c, int max);
 static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
 static char *int_to_roman(int number);
@@ -2256,21 +2299,26 @@ strspace_len(char *str)
  *
  * Puke if the date mode has already been set, and the caller attempts to set
  * it to a conflicting mode.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
-from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode)
+from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode, bool *have_error)
 {
 	if (mode != FROM_CHAR_DATE_NONE)
 	{
 		if (tmfc->mode == FROM_CHAR_DATE_NONE)
 			tmfc->mode = mode;
 		else if (tmfc->mode != mode)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("invalid combination of date conventions"),
-					 errhint("Do not mix Gregorian and ISO week date "
-							 "conventions in a formatting template.")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("invalid combination of date conventions"),
+								  errhint("Do not mix Gregorian and ISO week date "
+										  "conventions in a formatting template."))));
 	}
+
+on_error:
+	return;
 }
 
 /*
@@ -2278,18 +2326,25 @@ from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode)
  *
  * Puke if the destination integer has previously been set to some other
  * non-zero value.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
-from_char_set_int(int *dest, const int value, const FormatNode *node)
+from_char_set_int(int *dest, const int value, const FormatNode *node,
+				  bool *have_error)
 {
 	if (*dest != 0 && *dest != value)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("conflicting values for \"%s\" field in formatting string",
-						node->key->name),
-				 errdetail("This value contradicts a previous setting for "
-						   "the same field type.")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("conflicting values for \"%s\" field in "
+									 "formatting string",
+									 node->key->name),
+							  errdetail("This value contradicts a previous setting "
+										"for the same field type."))));
 	*dest = value;
+
+on_error:
+	return;
 }
 
 /*
@@ -2311,9 +2366,13 @@ from_char_set_int(int *dest, const int value, const FormatNode *node)
  * Note that from_char_parse_int() provides a more convenient wrapper where
  * the length of the field is the same as the length of the format keyword (as
  * with DD and MI).
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and -1 is returned.
  */
 static int
-from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
+from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node,
+						bool *have_error)
 {
 	long		result;
 	char		copy[DCH_MAX_ITEM_SIZ + 1];
@@ -2346,51 +2405,60 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
 		char	   *last;
 
 		if (used < len)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("source string too short for \"%s\" formatting field",
-							node->key->name),
-					 errdetail("Field requires %d characters, but only %d "
-							   "remain.",
-							   len, used),
-					 errhint("If your source string is not fixed-width, try "
-							 "using the \"FM\" modifier.")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("source string too short for \"%s\" "
+										 "formatting field",
+										 node->key->name),
+								  errdetail("Field requires %d characters, "
+											"but only %d remain.",
+											len, used),
+								  errhint("If your source string is not fixed-width, "
+										  "try using the \"FM\" modifier."))));
 
 		errno = 0;
 		result = strtol(copy, &last, 10);
 		used = last - copy;
 
 		if (used > 0 && used < len)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("invalid value \"%s\" for \"%s\"",
-							copy, node->key->name),
-					 errdetail("Field requires %d characters, but only %d "
-							   "could be parsed.", len, used),
-					 errhint("If your source string is not fixed-width, try "
-							 "using the \"FM\" modifier.")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("invalid value \"%s\" for \"%s\"",
+										 copy, node->key->name),
+								  errdetail("Field requires %d characters, "
+											"but only %d could be parsed.",
+											len, used),
+								  errhint("If your source string is not fixed-width, "
+										  "try using the \"FM\" modifier."))));
 
 		*src += used;
 	}
 
 	if (*src == init)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("invalid value \"%s\" for \"%s\"",
-						copy, node->key->name),
-				 errdetail("Value must be an integer.")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("invalid value \"%s\" for \"%s\"",
+									 copy, node->key->name),
+							  errdetail("Value must be an integer."))));
 
 	if (errno == ERANGE || result < INT_MIN || result > INT_MAX)
-		ereport(ERROR,
-				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-				 errmsg("value for \"%s\" in source string is out of range",
-						node->key->name),
-				 errdetail("Value must be in the range %d to %d.",
-						   INT_MIN, INT_MAX)));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							  errmsg("value for \"%s\" in source string is out of range",
+									 node->key->name),
+							  errdetail("Value must be in the range %d to %d.",
+										INT_MIN, INT_MAX))));
 
 	if (dest != NULL)
-		from_char_set_int(dest, (int) result, node);
+	{
+		from_char_set_int(dest, (int) result, node, have_error);
+		CHECK_ERROR;
+	}
+
 	return *src - init;
+
+on_error:
+	return -1;
 }
 
 /*
@@ -2403,9 +2471,9 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
  * required length explicitly.
  */
 static int
-from_char_parse_int(int *dest, char **src, FormatNode *node)
+from_char_parse_int(int *dest, char **src, FormatNode *node, bool *have_error)
 {
-	return from_char_parse_int_len(dest, src, node->key->len, node);
+	return from_char_parse_int_len(dest, src, node->key->len, node, have_error);
 }
 
 /* ----------
@@ -2488,11 +2556,12 @@ seq_search(char *name, const char *const *array, int type, int max, int *len)
  * pointed to by 'dest', advance 'src' to the end of the part of the string
  * which matched, and return the number of characters consumed.
  *
- * If the string doesn't match, throw an error.
+ * If the string doesn't match, throw an error if 'have_error' is NULL,
+ * otherwise set '*have_error' and return -1.
  */
 static int
-from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max,
-					 FormatNode *node)
+from_char_seq_search(int *dest, char **src, const char *const *array, int type,
+					 int max, FormatNode *node, bool *have_error)
 {
 	int			len;
 
@@ -2504,15 +2573,18 @@ from_char_seq_search(int *dest, char **src, const char *const *array, int type,
 		Assert(max <= DCH_MAX_ITEM_SIZ);
 		strlcpy(copy, *src, max + 1);
 
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("invalid value \"%s\" for \"%s\"",
-						copy, node->key->name),
-				 errdetail("The given value did not match any of the allowed "
-						   "values for this field.")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("invalid value \"%s\" for \"%s\"",
+									 copy, node->key->name),
+							  errdetail("The given value did not match any of "
+										"the allowed values for this field."))));
 	}
 	*src += len;
 	return len;
+
+on_error:
+	return -1;
 }
 
 /* ----------
@@ -3114,10 +3186,13 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
  *
  * Note: we currently don't have any to_interval() function, so there
  * is no need here for INVALID_FOR_INTERVAL checks.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  * ----------
  */
 static void
-DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
+DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std,
+			  bool *have_error)
 {
 	FormatNode *n;
 	char	   *s;
@@ -3157,10 +3232,10 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 				if (*s == n->character[0])
 					s++;
 				else
-					ereport(ERROR,
-							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-							 errmsg("unmatched format separator \"%c\"",
-									n->character[0])));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+										  errmsg("unmatched format separator \"%c\"",
+												 n->character[0]))));
 			}
 			else if (!fx_mode)
 			{
@@ -3216,7 +3291,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 			continue;
 		}
 
-		from_char_set_mode(out, n->key->date_mode);
+		from_char_set_mode(out, n->key->date_mode, have_error);
+		CHECK_ERROR;
 
 		switch (n->key->id)
 		{
@@ -3228,8 +3304,10 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 			case DCH_a_m:
 			case DCH_p_m:
 				from_char_seq_search(&value, &s, ampm_strings_long,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->pm, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->pm, value % 2, n, have_error);
+				CHECK_ERROR;
 				out->clock = CLOCK_12_HOUR;
 				break;
 			case DCH_AM:
@@ -3237,30 +3315,37 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 			case DCH_am:
 			case DCH_pm:
 				from_char_seq_search(&value, &s, ampm_strings,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->pm, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->pm, value % 2, n, have_error);
+				CHECK_ERROR;
 				out->clock = CLOCK_12_HOUR;
 				break;
 			case DCH_HH:
 			case DCH_HH12:
-				from_char_parse_int_len(&out->hh, &s, 2, n);
+				from_char_parse_int_len(&out->hh, &s, 2, n, have_error);
+				CHECK_ERROR;
 				out->clock = CLOCK_12_HOUR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_HH24:
-				from_char_parse_int_len(&out->hh, &s, 2, n);
+				from_char_parse_int_len(&out->hh, &s, 2, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_MI:
-				from_char_parse_int(&out->mi, &s, n);
+				from_char_parse_int(&out->mi, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_SS:
-				from_char_parse_int(&out->ss, &s, n);
+				from_char_parse_int(&out->ss, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_MS:		/* millisecond */
-				len = from_char_parse_int_len(&out->ms, &s, 3, n);
+				len = from_char_parse_int_len(&out->ms, &s, 3, n, have_error);
+				CHECK_ERROR;
 
 				/*
 				 * 25 is 0.25 and 250 is 0.25 too; 025 is 0.025 and not 0.25
@@ -3281,7 +3366,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 			case DCH_US:		/* microsecond */
 				len = from_char_parse_int_len(&out->us, &s,
 											  n->key->id == DCH_US ? 6 :
-											  out->ff, n);
+											  out->ff, n, have_error);
+				CHECK_ERROR;
 
 				out->us *= len == 1 ? 100000 :
 					len == 2 ? 10000 :
@@ -3292,16 +3378,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_SSSS:
-				from_char_parse_int(&out->ssss, &s, n);
+				from_char_parse_int(&out->ssss, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_tz:
 			case DCH_TZ:
 			case DCH_OF:
-				ereport(ERROR,
-						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						 errmsg("formatting field \"%s\" is only supported in to_char",
-								n->key->name)));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									  errmsg("formatting field \"%s\" is only supported in to_char",
+											 n->key->name))));
+				CHECK_ERROR;
 				break;
 			case DCH_TZH:
 
@@ -3325,82 +3413,102 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 						out->tzsign = +1;
 				}
 
-				from_char_parse_int_len(&out->tzh, &s, 2, n);
+				from_char_parse_int_len(&out->tzh, &s, 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_TZM:
 				/* assign positive timezone sign if TZH was not seen before */
 				if (!out->tzsign)
 					out->tzsign = +1;
-				from_char_parse_int_len(&out->tzm, &s, 2, n);
+				from_char_parse_int_len(&out->tzm, &s, 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_A_D:
 			case DCH_B_C:
 			case DCH_a_d:
 			case DCH_b_c:
 				from_char_seq_search(&value, &s, adbc_strings_long,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->bc, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->bc, value % 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_AD:
 			case DCH_BC:
 			case DCH_ad:
 			case DCH_bc:
 				from_char_seq_search(&value, &s, adbc_strings,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->bc, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->bc, value % 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_MONTH:
 			case DCH_Month:
 			case DCH_month:
 				from_char_seq_search(&value, &s, months_full, ONE_UPPER,
-									 MAX_MONTH_LEN, n);
-				from_char_set_int(&out->mm, value + 1, n);
+									 MAX_MONTH_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, value + 1, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_MON:
 			case DCH_Mon:
 			case DCH_mon:
 				from_char_seq_search(&value, &s, months, ONE_UPPER,
-									 MAX_MON_LEN, n);
-				from_char_set_int(&out->mm, value + 1, n);
+									 MAX_MON_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, value + 1, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_MM:
-				from_char_parse_int(&out->mm, &s, n);
+				from_char_parse_int(&out->mm, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_DAY:
 			case DCH_Day:
 			case DCH_day:
 				from_char_seq_search(&value, &s, days, ONE_UPPER,
-									 MAX_DAY_LEN, n);
-				from_char_set_int(&out->d, value, n);
+									 MAX_DAY_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->d, value, n, have_error);
+				CHECK_ERROR;
 				out->d++;
 				break;
 			case DCH_DY:
 			case DCH_Dy:
 			case DCH_dy:
 				from_char_seq_search(&value, &s, days, ONE_UPPER,
-									 MAX_DY_LEN, n);
-				from_char_set_int(&out->d, value, n);
+									 MAX_DY_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->d, value, n, have_error);
+				CHECK_ERROR;
 				out->d++;
 				break;
 			case DCH_DDD:
-				from_char_parse_int(&out->ddd, &s, n);
+				from_char_parse_int(&out->ddd, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_IDDD:
-				from_char_parse_int_len(&out->ddd, &s, 3, n);
+				from_char_parse_int_len(&out->ddd, &s, 3, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_DD:
-				from_char_parse_int(&out->dd, &s, n);
+				from_char_parse_int(&out->dd, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_D:
-				from_char_parse_int(&out->d, &s, n);
+				from_char_parse_int(&out->d, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_ID:
-				from_char_parse_int_len(&out->d, &s, 1, n);
+				from_char_parse_int_len(&out->d, &s, 1, n, have_error);
+				CHECK_ERROR;
 				/* Shift numbering to match Gregorian where Sunday = 1 */
 				if (++out->d > 7)
 					out->d = 1;
@@ -3408,7 +3516,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 				break;
 			case DCH_WW:
 			case DCH_IW:
-				from_char_parse_int(&out->ww, &s, n);
+				from_char_parse_int(&out->ww, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Q:
@@ -3423,11 +3532,13 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 				 * We still parse the source string for an integer, but it
 				 * isn't stored anywhere in 'out'.
 				 */
-				from_char_parse_int((int *) NULL, &s, n);
+				from_char_parse_int((int *) NULL, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_CC:
-				from_char_parse_int(&out->cc, &s, n);
+				from_char_parse_int(&out->cc, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Y_YYY:
@@ -3439,11 +3550,12 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 
 					matched = sscanf(s, "%d,%03d%n", &millennia, &years, &nch);
 					if (matched < 2)
-						ereport(ERROR,
-								(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-								 errmsg("invalid input string for \"Y,YYY\"")));
+						RETURN_ERROR(ereport(ERROR,
+											 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+											  errmsg("invalid input string for \"Y,YYY\""))));
 					years += (millennia * 1000);
-					from_char_set_int(&out->year, years, n);
+					from_char_set_int(&out->year, years, n, have_error);
+					CHECK_ERROR;
 					out->yysz = 4;
 					s += nch;
 					SKIP_THth(s, n->suffix);
@@ -3452,53 +3564,66 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 			case DCH_RRRR:
 			case DCH_YYYY:
 			case DCH_IYYY:
-				from_char_parse_int(&out->year, &s, n);
+				from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
 				out->yysz = 4;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_YYY:
 			case DCH_IYY:
-				len = from_char_parse_int(&out->year, &s, n);
+				len = from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
 				out->year = adjust_partial_year_to_2020(out->year, len);
 				out->yysz = 3;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_RR:
-				from_char_parse_int(&out->year, &s, n);
+				from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
 				out->year = adjust_partial_round_year_to_2020(out->year);
 				out->yysz = 2;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_YY:
 			case DCH_IY:
-				len = from_char_parse_int(&out->year, &s, n);
+				len = from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
 				out->year = adjust_partial_year_to_2020(out->year, len);
 				out->yysz = 2;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Y:
 			case DCH_I:
-				len = from_char_parse_int(&out->year, &s, n);
+				len = from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
 				out->year = adjust_partial_year_to_2020(out->year, len);
 				out->yysz = 1;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_RM:
 				from_char_seq_search(&value, &s, rm_months_upper,
-									 ALL_UPPER, MAX_RM_LEN, n);
-				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n);
+									 ALL_UPPER, MAX_RM_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value,
+								  n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_rm:
 				from_char_seq_search(&value, &s, rm_months_lower,
-									 ALL_LOWER, MAX_RM_LEN, n);
-				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n);
+									 ALL_LOWER, MAX_RM_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value,
+								  n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_W:
-				from_char_parse_int(&out->w, &s, n);
+				from_char_parse_int(&out->w, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_J:
-				from_char_parse_int(&out->j, &s, n);
+				from_char_parse_int(&out->j, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 		}
@@ -3522,19 +3647,22 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 	if (std)
 	{
 		if (n->type != NODE_TYPE_END)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("input string is too short for datetime format")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("input string is too short for datetime format"))));
 
 		while (*s != '\0' && isspace((unsigned char) *s))
 			s++;
 
 		if (*s != '\0')
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("trailing characters remain in input string after "
-							"datetime format")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("trailing characters remain in input string "
+										 "after datetime format"))));
 	}
+
+on_error:
+	return;
 }
 
 /*
@@ -3555,9 +3683,13 @@ DCH_prevent_counter_overflow(void)
 	}
 }
 
-/* Get mask of date/time/zone components present in format nodes. */
+/*
+ * Get mask of date/time/zone components present in format nodes.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
+ */
 static int
-DCH_datetime_type(FormatNode *node)
+DCH_datetime_type(FormatNode *node, bool *have_error)
 {
 	FormatNode *n;
 	int			flags = 0;
@@ -3598,10 +3730,10 @@ DCH_datetime_type(FormatNode *node)
 			case DCH_tz:
 			case DCH_TZ:
 			case DCH_OF:
-				ereport(ERROR,
-						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						 errmsg("formatting field \"%s\" is only supported in to_char",
-								n->key->name)));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									  errmsg("formatting field \"%s\" is only supported in to_char",
+											 n->key->name))));
 				flags |= DCH_ZONED;
 				break;
 			case DCH_TZH:
@@ -3657,6 +3789,7 @@ DCH_datetime_type(FormatNode *node)
 		}
 	}
 
+on_error:
 	return flags;
 }
 
@@ -3951,7 +4084,7 @@ to_timestamp(PG_FUNCTION_ARGS)
 	fsec_t		fsec;
 	int			fprec;
 
-	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL, NULL);
 
 	/* Use the specified time zone, if any. */
 	if (tm.tm_zone)
@@ -3990,7 +4123,7 @@ to_date(PG_FUNCTION_ARGS)
 	struct pg_tm tm;
 	fsec_t		fsec;
 
-	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL, NULL);
 
 	/* Prevent overflow in Julian-day routines */
 	if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -4017,17 +4150,21 @@ to_date(PG_FUNCTION_ARGS)
  * the presence of date/time/zone components in the format string.
  *
  * When timezone component is present, the corresponding offset is set to '*tz'.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero value is returned.
  */
 Datum
 parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
-			   int32 *typmod, int *tz)
+			   int32 *typmod, int *tz, bool *have_error)
 {
 	struct pg_tm tm;
 	fsec_t		fsec;
 	int			fprec = 0;
 	uint32		flags;
 
-	do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags);
+	do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags, have_error);
+	CHECK_ERROR;
 
 	*typmod = fprec ? fprec : -1;	/* fractional part precision */
 
@@ -4055,15 +4192,15 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 					 */
 					Assert(!strict);
 
-					ereport(ERROR,
-							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-							 errmsg("missing time zone in input string for type timestamptz")));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+										  errmsg("missing time zone in input string for type timestamptz"))));
 				}
 
 				if (tm2timestamp(&tm, fsec, tz, &result) != 0)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("timestamptz out of range")));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("timestamptz out of range"))));
 
 				AdjustTimestampForTypmod(&result, *typmod);
 
@@ -4075,9 +4212,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 				Timestamp	result;
 
 				if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("timestamp out of range")));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("timestamp out of range"))));
 
 				AdjustTimestampForTypmod(&result, *typmod);
 
@@ -4089,9 +4226,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 		{
 			if (flags & DCH_ZONED)
 			{
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-						 errmsg("datetime format is zoned but not timed")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+									  errmsg("datetime format is zoned but not timed"))));
 			}
 			else
 			{
@@ -4099,20 +4236,20 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 
 				/* Prevent overflow in Julian-day routines */
 				if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("date out of range: \"%s\"",
-									text_to_cstring(date_txt))));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("date out of range: \"%s\"",
+												 text_to_cstring(date_txt)))));
 
 				result = 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(result))
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("date out of range: \"%s\"",
-									text_to_cstring(date_txt))));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("date out of range: \"%s\"",
+												 text_to_cstring(date_txt)))));
 
 				*typid = DATEOID;
 				return DateADTGetDatum(result);
@@ -4130,7 +4267,7 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 				int			dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
 
 				if (dterr)
-					DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz");
+					RETURN_ERROR(DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz"));
 			}
 			else
 			{
@@ -4141,15 +4278,15 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 				 */
 				Assert(!strict);
 
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-						 errmsg("missing time zone in input string for type timetz")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+									  errmsg("missing time zone in input string for type timetz"))));
 			}
 
 			if (tm2timetz(&tm, fsec, *tz, result) != 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-						 errmsg("timetz out of range")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+									  errmsg("timetz out of range"))));
 
 			AdjustTimeForTypmod(&result->time, *typmod);
 
@@ -4161,9 +4298,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 			TimeADT		result;
 
 			if (tm2time(&tm, fsec, &result) != 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-						 errmsg("time out of range")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+									  errmsg("time out of range"))));
 
 			AdjustTimeForTypmod(&result, *typmod);
 
@@ -4173,11 +4310,12 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 	}
 	else
 	{
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("datetime format is not dated and not timed")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("datetime format is not dated and not timed"))));
 	}
 
+on_error:
 	return (Datum) 0;
 }
 
@@ -4193,17 +4331,24 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
  *
  * The TmFromChar is then analysed and converted into the final results in
  * struct 'tm' and 'fsec'.
+ *
+ * Bit mask of date/time/zone components found in 'fmt' is returned in 'flags'.
+ *
+ * 'std' specifies standard parsing mode.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
 do_to_timestamp(text *date_txt, text *fmt, bool std,
 				struct pg_tm *tm, fsec_t *fsec, int *fprec,
-				uint32 *flags)
+				uint32 *flags, bool *have_error)
 {
-	FormatNode *format;
+	FormatNode *format = NULL;
 	TmFromChar	tmfc;
 	int			fmt_len;
 	char	   *date_str;
 	int			fmask;
+	bool		incache = false;
 
 	date_str = text_to_cstring(date_txt);
 
@@ -4217,7 +4362,6 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 	if (fmt_len)
 	{
 		char	   *fmt_str;
-		bool		incache;
 
 		fmt_str = text_to_cstring(fmt);
 
@@ -4227,8 +4371,6 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 			 * Allocate new memory if format picture is bigger than static
 			 * cache and do not use cache (call parser always)
 			 */
-			incache = false;
-
 			format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode));
 
 			parse_format(format, fmt_str, DCH_keywords, DCH_suff, DCH_index,
@@ -4250,15 +4392,21 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 		/* dump_index(DCH_keywords, DCH_index); */
 #endif
 
-		DCH_from_char(format, date_str, &tmfc, std);
+		DCH_from_char(format, date_str, &tmfc, std, have_error);
+		CHECK_ERROR;
 
 		pfree(fmt_str);
 
 		if (flags)
-			*flags = DCH_datetime_type(format);
+			*flags = DCH_datetime_type(format, have_error);
 
 		if (!incache)
+		{
 			pfree(format);
+			format = NULL;
+		}
+
+		CHECK_ERROR;
 	}
 
 	DEBUG_TMFC(&tmfc);
@@ -4287,11 +4435,13 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 	if (tmfc.clock == CLOCK_12_HOUR)
 	{
 		if (tm->tm_hour < 1 || tm->tm_hour > HOURS_PER_DAY / 2)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("hour \"%d\" is invalid for the 12-hour clock",
-							tm->tm_hour),
-					 errhint("Use the 24-hour clock, or give an hour between 1 and 12.")));
+		{
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("hour \"%d\" is invalid for the 12-hour clock",
+										 tm->tm_hour),
+								  errhint("Use the 24-hour clock, or give an hour between 1 and 12."))));
+		}
 
 		if (tmfc.pm && tm->tm_hour < HOURS_PER_DAY / 2)
 			tm->tm_hour += HOURS_PER_DAY / 2;
@@ -4395,9 +4545,11 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 		 */
 
 		if (!tm->tm_year && !tmfc.bc)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("cannot calculate day of year without year information")));
+		{
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("cannot calculate day of year without year information"))));
+		}
 
 		if (tmfc.mode == FROM_CHAR_DATE_ISOWEEK)
 		{
@@ -4454,7 +4606,7 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 			 * said DTERR_MD_FIELD_OVERFLOW, because we don't want to print an
 			 * irrelevant hint about datestyle.
 			 */
-			DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp");
+			RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp"));
 		}
 	}
 
@@ -4463,7 +4615,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 		tm->tm_min < 0 || tm->tm_min >= MINS_PER_HOUR ||
 		tm->tm_sec < 0 || tm->tm_sec >= SECS_PER_MINUTE ||
 		*fsec < INT64CONST(0) || *fsec >= USECS_PER_SEC)
-		DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp");
+	{
+		RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp"));
+	}
 
 	/* Save parsed time-zone into tm->tm_zone if it was specified */
 	if (tmfc.tzsign)
@@ -4472,7 +4626,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 
 		if (tmfc.tzh < 0 || tmfc.tzh > MAX_TZDISP_HOUR ||
 			tmfc.tzm < 0 || tmfc.tzm >= MINS_PER_HOUR)
-			DateTimeParseError(DTERR_TZDISP_OVERFLOW, date_str, "timestamp");
+		{
+			RETURN_ERROR(DateTimeParseError(DTERR_TZDISP_OVERFLOW, date_str, "timestamp"));
+		}
 
 		tz = psprintf("%c%02d:%02d",
 					  tmfc.tzsign > 0 ? '+' : '-', tmfc.tzh, tmfc.tzm);
@@ -4482,6 +4638,11 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 
 	DEBUG_TM(tm);
 
+on_error:
+
+	if (format && !incache)
+		pfree(format);
+
 	pfree(date_str);
 }
 
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 2931bd58c0d..84bc97d40c3 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -329,11 +329,11 @@ timestamp_scale(PG_FUNCTION_ARGS)
 }
 
 /*
- * AdjustTimestampForTypmod --- round off a timestamp to suit given typmod
+ * AdjustTimestampForTypmodError --- round off a timestamp to suit given typmod
  * Works for either timestamp or timestamptz.
  */
-void
-AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
+bool
+AdjustTimestampForTypmodError(Timestamp *time, int32 typmod, bool *error)
 {
 	static const int64 TimestampScales[MAX_TIMESTAMP_PRECISION + 1] = {
 		INT64CONST(1000000),
@@ -359,10 +359,18 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
 		&& (typmod != -1) && (typmod != MAX_TIMESTAMP_PRECISION))
 	{
 		if (typmod < 0 || typmod > MAX_TIMESTAMP_PRECISION)
+		{
+			if (error)
+			{
+				*error = true;
+				return false;
+			}
+
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("timestamp(%d) precision must be between %d and %d",
 							typmod, 0, MAX_TIMESTAMP_PRECISION)));
+		}
 
 		if (*time >= INT64CONST(0))
 		{
@@ -375,8 +383,15 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
 					  * TimestampScales[typmod]);
 		}
 	}
+
+	return true;
 }
 
+void
+AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
+{
+	(void) AdjustTimestampForTypmodError(time, typmod, NULL);
+}
 
 /* timestamptz_in()
  * Convert a string to internal form.
@@ -5172,8 +5187,15 @@ timestamp_timestamptz(PG_FUNCTION_ARGS)
 	PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
 }
 
-static TimestampTz
-timestamp2timestamptz(Timestamp timestamp)
+/*
+ * Convert timestamp to timestamp with time zone.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero is returned.
+ */
+
+TimestampTz
+timestamp2timestamptz_opt_error(Timestamp timestamp, bool *have_error)
 {
 	TimestampTz result;
 	struct pg_tm tt,
@@ -5182,23 +5204,33 @@ timestamp2timestamptz(Timestamp timestamp)
 	int			tz;
 
 	if (TIMESTAMP_NOT_FINITE(timestamp))
-		result = timestamp;
-	else
-	{
-		if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("timestamp out of range")));
+		return timestamp;
 
+	if (!timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL))
+	{
 		tz = DetermineTimeZoneOffset(tm, session_timezone);
 
-		if (tm2timestamp(tm, fsec, &tz, &result) != 0)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("timestamp out of range")));
+		if (!tm2timestamp(tm, fsec, &tz, &result))
+			return result;
 	}
 
-	return result;
+	if (have_error)
+		*have_error = true;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+				 errmsg("timestamp out of range")));
+
+	return 0;
+}
+
+/*
+ * Single-argument version of timestamp2timestamptz_opt_error().
+ */
+static TimestampTz
+timestamp2timestamptz(Timestamp timestamp)
+{
+	return timestamp2timestamptz_opt_error(timestamp, NULL);
 }
 
 /* timestamptz_timestamp()
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index bd15bfa5bb0..c29f13aaf04 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -70,6 +70,8 @@ typedef struct
 /* date.c */
 extern int32 anytime_typmod_check(bool istz, int32 typmod);
 extern double date2timestamp_no_overflow(DateADT dateVal);
+extern Timestamp date2timestamp_opt_error(DateADT dateVal, bool *have_error);
+extern TimestampTz date2timestamptz_opt_error(DateADT dateVal, bool *have_error);
 extern void EncodeSpecialDate(DateADT dt, char *str);
 extern DateADT GetSQLCurrentDate(void);
 extern TimeTzADT *GetSQLCurrentTime(int32 typmod);
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index 0cafdd26538..5ebf336071b 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -337,5 +337,7 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
 extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
 
 extern void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
+extern bool AdjustTimestampForTypmodError(Timestamp *time, int32 typmod,
+										  bool *error);
 
 #endif							/* DATETIME_H */
diff --git a/src/include/utils/formatting.h b/src/include/utils/formatting.h
index beeaf10c332..165ca78ba5c 100644
--- a/src/include/utils/formatting.h
+++ b/src/include/utils/formatting.h
@@ -27,6 +27,7 @@ extern char *asc_toupper(const char *buff, size_t nbytes);
 extern char *asc_initcap(const char *buff, size_t nbytes);
 
 extern Datum parse_datetime(text *date_txt, text *fmt, bool std,
-							Oid *typid, int32 *typmod, int *tz);
+							Oid *typid, int32 *typmod, int *tz,
+							bool *have_error);
 
 #endif
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index ea16190ec3d..e884d4405f5 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -97,6 +97,9 @@ extern int	timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
 /* timestamp comparison works for timestamptz also */
 #define timestamptz_cmp_internal(dt1,dt2)	timestamp_cmp_internal(dt1, dt2)
 
+extern TimestampTz timestamp2timestamptz_opt_error(Timestamp timestamp,
+												   bool *have_error);
+
 extern int	isoweek2j(int year, int week);
 extern void isoweek2date(int woy, int *year, int *mon, int *mday);
 extern void isoweekdate2date(int isoweek, int wday, int *year, int *mon, int *mday);
-- 
2.14.3

0007-Allow-datetime-values-in-JsonbValue-6.patchapplication/octet-stream; name=0007-Allow-datetime-values-in-JsonbValue-6.patchDownload
From 491f6ea83c3de13626775eda1da67b82756177d7 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Sat, 20 Jul 2019 21:50:48 +0300
Subject: [PATCH 7/8] Allow datetime values in JsonbValue

SQL/JSON standard allows manipulation with datetime values.  So, it appears to
be convinient to allow datetime values to be represented in JsonbValue struct.
These datetime values are allowed for temporary representation only.  During
serialization datetime values are converted into strings.

SQL/JSON requires writing timestamps with timezone in the same timezone offset
as they were parsed.  This is why we allow storage of timezone offset in
JsonbValue struct.  For the same reason timezone offset argument is added to
JsonEncodeDateTime() function.

Extracted from original patch by Nikita Glukhov, Teodor Sigaev, Oleg Bartunov.
Revised by me.  Comments were adjusted by Liudmila Mantrova.

Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
Discussion: https://postgr.es/m/CAPpHfdsZgYEra_PeCLGNoXOWYx6iU-S3wF8aX0ObQUcZU%2B4XTw%40mail.gmail.com
Author: Nikita Glukhov, Teodor Sigaev, Oleg Bartunov, Alexander Korotkov, Liudmila Mantrova
Reviewed-by: Anastasia Lubennikova, Peter Eisentraut
---
 src/backend/utils/adt/json.c       | 32 ++++++++++++++++++++++++++------
 src/backend/utils/adt/jsonb.c      | 27 ++++++++++++++++++++++++---
 src/backend/utils/adt/jsonb_util.c | 21 +++++++++++++++++++++
 src/include/utils/jsonapi.h        |  3 ++-
 src/include/utils/jsonb.h          | 24 +++++++++++++++++++++---
 5 files changed, 94 insertions(+), 13 deletions(-)

diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index 26d293709aa..d4ba3bd87db 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -1506,7 +1506,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 			{
 				char		buf[MAXDATELEN + 1];
 
-				JsonEncodeDateTime(buf, val, DATEOID);
+				JsonEncodeDateTime(buf, val, DATEOID, NULL);
 				appendStringInfo(result, "\"%s\"", buf);
 			}
 			break;
@@ -1514,7 +1514,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 			{
 				char		buf[MAXDATELEN + 1];
 
-				JsonEncodeDateTime(buf, val, TIMESTAMPOID);
+				JsonEncodeDateTime(buf, val, TIMESTAMPOID, NULL);
 				appendStringInfo(result, "\"%s\"", buf);
 			}
 			break;
@@ -1522,7 +1522,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 			{
 				char		buf[MAXDATELEN + 1];
 
-				JsonEncodeDateTime(buf, val, TIMESTAMPTZOID);
+				JsonEncodeDateTime(buf, val, TIMESTAMPTZOID, NULL);
 				appendStringInfo(result, "\"%s\"", buf);
 			}
 			break;
@@ -1550,10 +1550,11 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 
 /*
  * Encode 'value' of datetime type 'typid' into JSON string in ISO format using
- * optionally preallocated buffer 'buf'.
+ * optionally preallocated buffer 'buf'.  Optional 'tzp' determines time-zone
+ * offset (in seconds) in which we want to show timestamptz.
  */
 char *
-JsonEncodeDateTime(char *buf, Datum value, Oid typid)
+JsonEncodeDateTime(char *buf, Datum value, Oid typid, const int *tzp)
 {
 	if (!buf)
 		buf = palloc(MAXDATELEN + 1);
@@ -1630,11 +1631,30 @@ JsonEncodeDateTime(char *buf, Datum value, Oid typid)
 				const char *tzn = NULL;
 
 				timestamp = DatumGetTimestampTz(value);
+
+				/*
+				 * If a time zone is specified, we apply the time-zone shift,
+				 * convert timestamptz to pg_tm as if it were without a time
+				 * zone, and then use the specified time zone for converting
+				 * the timestamp into a string.
+				 */
+				if (tzp)
+				{
+					tz = *tzp;
+					timestamp -= (TimestampTz) tz * USECS_PER_SEC;
+				}
+
 				/* Same as timestamptz_out(), but forcing DateStyle */
 				if (TIMESTAMP_NOT_FINITE(timestamp))
 					EncodeSpecialTimestamp(timestamp, buf);
-				else if (timestamp2tm(timestamp, &tz, &tm, &fsec, &tzn, NULL) == 0)
+				else if (timestamp2tm(timestamp, tzp ? NULL : &tz, &tm, &fsec,
+									  tzp ? NULL : &tzn, NULL) == 0)
+				{
+					if (tzp)
+						tm.tm_isdst = 1;	/* set time-zone presence flag */
+
 					EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf);
+				}
 				else
 					ereport(ERROR,
 							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 69f41ab4556..74b4bbe44c6 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -206,6 +206,24 @@ JsonbTypeName(JsonbValue *jbv)
 			return "boolean";
 		case jbvNull:
 			return "null";
+		case jbvDatetime:
+			switch (jbv->val.datetime.typid)
+			{
+				case DATEOID:
+					return "date";
+				case TIMEOID:
+					return "time without time zone";
+				case TIMETZOID:
+					return "time with time zone";
+				case TIMESTAMPOID:
+					return "timestamp without time zone";
+				case TIMESTAMPTZOID:
+					return "timestamp with time zone";
+				default:
+					elog(ERROR, "unrecognized jsonb value datetime type: %d",
+						 jbv->val.datetime.typid);
+			}
+			return "unknown";
 		default:
 			elog(ERROR, "unrecognized jsonb value type: %d", jbv->type);
 			return "unknown";
@@ -805,17 +823,20 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result,
 				break;
 			case JSONBTYPE_DATE:
 				jb.type = jbvString;
-				jb.val.string.val = JsonEncodeDateTime(NULL, val, DATEOID);
+				jb.val.string.val = JsonEncodeDateTime(NULL, val,
+													   DATEOID, NULL);
 				jb.val.string.len = strlen(jb.val.string.val);
 				break;
 			case JSONBTYPE_TIMESTAMP:
 				jb.type = jbvString;
-				jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPOID);
+				jb.val.string.val = JsonEncodeDateTime(NULL, val,
+													   TIMESTAMPOID, NULL);
 				jb.val.string.len = strlen(jb.val.string.val);
 				break;
 			case JSONBTYPE_TIMESTAMPTZ:
 				jb.type = jbvString;
-				jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPTZOID);
+				jb.val.string.val = JsonEncodeDateTime(NULL, val,
+													   TIMESTAMPTZOID, NULL);
 				jb.val.string.len = strlen(jb.val.string.val);
 				break;
 			case JSONBTYPE_JSONCAST:
diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index ac04c4a57bc..dee6e1bef8c 100644
--- a/src/backend/utils/adt/jsonb_util.c
+++ b/src/backend/utils/adt/jsonb_util.c
@@ -14,9 +14,12 @@
 #include "postgres.h"
 
 #include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
 #include "miscadmin.h"
 #include "utils/builtins.h"
+#include "utils/datetime.h"
 #include "utils/hashutils.h"
+#include "utils/jsonapi.h"
 #include "utils/jsonb.h"
 #include "utils/memutils.h"
 #include "utils/varlena.h"
@@ -242,6 +245,8 @@ compareJsonbContainers(JsonbContainer *a, JsonbContainer *b)
 						break;
 					case jbvBinary:
 						elog(ERROR, "unexpected jbvBinary value");
+					case jbvDatetime:
+						elog(ERROR, "unexpected jbvDatetime value");
 				}
 			}
 			else
@@ -1749,6 +1754,22 @@ convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal)
 				JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE;
 			break;
 
+		case jbvDatetime:
+			{
+				char		buf[MAXDATELEN + 1];
+				size_t		len;
+
+				JsonEncodeDateTime(buf,
+								   scalarVal->val.datetime.value,
+								   scalarVal->val.datetime.typid,
+								   &scalarVal->val.datetime.tz);
+				len = strlen(buf);
+				appendToBuffer(buffer, buf, len);
+
+				*jentry = len;
+			}
+			break;
+
 		default:
 			elog(ERROR, "invalid jsonb scalar type");
 	}
diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h
index 1c56acca55a..e1dab24d5dd 100644
--- a/src/include/utils/jsonapi.h
+++ b/src/include/utils/jsonapi.h
@@ -161,6 +161,7 @@ extern Jsonb *transform_jsonb_string_values(Jsonb *jsonb, void *action_state,
 extern text *transform_json_string_values(text *json, void *action_state,
 										  JsonTransformStringValuesAction transform_action);
 
-extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid);
+extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid,
+								const int *tzp);
 
 #endif							/* JSONAPI_H */
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index ac52b75f51d..80ef327cc09 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -241,7 +241,15 @@ enum jbvType
 	jbvArray = 0x10,
 	jbvObject,
 	/* Binary (i.e. struct Jsonb) jbvArray/jbvObject */
-	jbvBinary
+	jbvBinary,
+
+	/*
+	 * Virtual types.
+	 *
+	 * These types are used only for in-memory JSON processing and serialized
+	 * into JSON strings when outputted to json/jsonb.
+	 */
+	jbvDatetime = 0x20,
 };
 
 /*
@@ -282,11 +290,21 @@ struct JsonbValue
 			int			len;
 			JsonbContainer *data;
 		}			binary;		/* Array or object, in on-disk format */
+
+		struct
+		{
+			Datum		value;
+			Oid			typid;
+			int32		typmod;
+			int			tz;		/* Numeric time zone, in seconds, for
+								 * TimestampTz data type */
+		}			datetime;
 	}			val;
 };
 
-#define IsAJsonbScalar(jsonbval)	((jsonbval)->type >= jbvNull && \
-									 (jsonbval)->type <= jbvBool)
+#define IsAJsonbScalar(jsonbval)	(((jsonbval)->type >= jbvNull && \
+									  (jsonbval)->type <= jbvBool) || \
+									  (jsonbval)->type == jbvDatetime)
 
 /*
  * Key/value pair within an Object.
-- 
2.14.3

#19Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Alexander Korotkov (#18)
6 attachment(s)
Re: Support for jsonpath .datetime() method

On Sat, Sep 14, 2019 at 10:18 PM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

On Tue, Aug 27, 2019 at 5:19 AM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

Revised patchset is attached. It still requires some polishing. But
the most doubtful part is handling of RR, YYY, YY and Y.

Standard requires us to complete YYY, YY and Y with high digits from
current year. So, if YY matches 99, then year should be 2099, not
1999.

For RR, standard requirements are relaxed. Implementation may choose
matching year from range [current_year - 100; current_year + 100]. It
looks reasonable to handle RR in the same way we currently handle YY:
select appropriate year in [1970; 2069] range. It seems like we
select this range to start in the same point as unix timestamp. But
nowadays it still looks reasonable: it's about +- 50 from current
year. So, years close to the current one are likely completed
correctly. In Oracle RR returns year in [1950; 1949] range. So, it
seems to be designed near 2000 :). I don't think we need to copy this
behavior.

Handling YYY and YY in standard way seems quite easy. We can complete
them as 2YYY and 20YY. This should be standard conforming till 2100.

But handling Y looks problematic. Immutable way of handling this
would work only for decade. Current code completes Y as 200Y and it
looks pretty "outdated" now in 2019. Using current real year would
make conversion timestamp-dependent. This property doesn't look favor
for to_date()/to_timestamp() and unacceptable for immutable jsonpath
functions (but we can forbid using Y pattern there). Current patch
complete Y as 202Y assuming v13 will be released in 2020. But I'm not
sure what is better solution here. The bright side is that I haven't
seen anybody use Y patten in real life :)

Revised patchset is attached. It adds and adjusts commit messages,
comments and does other cosmetic improvements.

I think 0001 and 0002 are well reviewed already. And these patches
are usable not only for jsonpath .datetime(), but contain improvements
for existing to_date()/to_timestamp() SQL functions. I'm going to
push these two if no objections.

Those two patches are pushed. Just before commit I've renamed
deciseconds to "tenths of seconds", sentiseconds to "hundredths of
seconds" as discussed before [1].

The rest of patchset is attached.

Links
1. /messages/by-id/0409fb42-18d3-bdb7-37ab-d742d5313a40@2ndQuadrant.com

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0002-Implement-standard-datetime-parsing-mode-7.patchapplication/octet-stream; name=0002-Implement-standard-datetime-parsing-mode-7.patchDownload
From c20d0272a55e2f929db399ad1805224664fca977 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Mon, 26 Aug 2019 04:40:24 +0300
Subject: [PATCH 2/6] Implement standard datetime parsing mode

SQL Standard 2016 defines rules for handling separators in datetime template
strings, which are different to to_date()/to_timestamp() rules.  Stardard
allows only small set of separators and requires strict matching for them.

Standard applies to jsonpath .datetime() method and CAST (... FORMAT ...) SQL
clause.  We're not going to change handling of separators in existing
to_date()/to_timestamp() functions, because their current behavior is familiar
for users.  Standard behavior now available by special flag, which will be used
in upcoming .datetime() jsonpath method.

Discussion: https://postgr.es/m/CAPpHfdsZgYEra_PeCLGNoXOWYx6iU-S3wF8aX0ObQUcZU%2B4XTw%40mail.gmail.com
Author: Alexander Korotkov
---
 src/backend/utils/adt/formatting.c | 145 +++++++++++++++++++++++++++----------
 1 file changed, 105 insertions(+), 40 deletions(-)

diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 65f0439ae9f..50a261adac5 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -99,11 +99,12 @@
 #include "utils/pg_locale.h"
 
 /* ----------
- * Routines type
+ * Routines flags
  * ----------
  */
-#define DCH_TYPE		1		/* DATE-TIME version	*/
-#define NUM_TYPE		2		/* NUMBER version	*/
+#define DCH_FLAG		0x1		/* DATE-TIME flag	*/
+#define NUM_FLAG		0x2		/* NUMBER flag	*/
+#define STD_FLAG		0x4		/* STANDARD flag	*/
 
 /* ----------
  * KeyWord Index (ascii from position 32 (' ') to 126 (~))
@@ -384,6 +385,7 @@ typedef struct
 {
 	FormatNode	format[DCH_CACHE_SIZE + 1];
 	char		str[DCH_CACHE_SIZE + 1];
+	bool		std;
 	bool		valid;
 	int			age;
 } DCHCacheEntry;
@@ -1008,11 +1010,12 @@ static const KeySuffix *suff_search(const char *str, const KeySuffix *suf, int t
 static bool is_separator_char(const char *str);
 static void NUMDesc_prepare(NUMDesc *num, FormatNode *n);
 static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
-						 const KeySuffix *suf, const int *index, int ver, NUMDesc *Num);
+						 const KeySuffix *suf, const int *index, uint32 flags, NUMDesc *Num);
 
 static void DCH_to_char(FormatNode *node, bool is_interval,
 						TmToChar *in, char *out, Oid collid);
-static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out);
+static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
+						  bool std);
 
 #ifdef DEBUG_TO_FROM_CHAR
 static void dump_index(const KeyWord *k, const int *index);
@@ -1030,7 +1033,7 @@ static int	from_char_parse_int_len(int *dest, char **src, const int len, FormatN
 static int	from_char_parse_int(int *dest, char **src, FormatNode *node);
 static int	seq_search(char *name, const char *const *array, int type, int max, int *len);
 static int	from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
-static void do_to_timestamp(text *date_txt, text *fmt,
+static void do_to_timestamp(text *date_txt, text *fmt, bool std,
 							struct pg_tm *tm, fsec_t *fsec, int *fprec);
 static char *fill_str(char *str, int c, int max);
 static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
@@ -1042,9 +1045,9 @@ static void NUM_numpart_to_char(NUMProc *Np, int id);
 static char *NUM_processor(FormatNode *node, NUMDesc *Num, char *inout,
 						   char *number, int input_len, int to_char_out_pre_spaces,
 						   int sign, bool is_to_char, Oid collid);
-static DCHCacheEntry *DCH_cache_getnew(const char *str);
-static DCHCacheEntry *DCH_cache_search(const char *str);
-static DCHCacheEntry *DCH_cache_fetch(const char *str);
+static DCHCacheEntry *DCH_cache_getnew(const char *str, bool std);
+static DCHCacheEntry *DCH_cache_search(const char *str, bool std);
+static DCHCacheEntry *DCH_cache_fetch(const char *str, bool std);
 static NUMCacheEntry *NUM_cache_getnew(const char *str);
 static NUMCacheEntry *NUM_cache_search(const char *str);
 static NUMCacheEntry *NUM_cache_fetch(const char *str);
@@ -1287,7 +1290,7 @@ NUMDesc_prepare(NUMDesc *num, FormatNode *n)
  */
 static void
 parse_format(FormatNode *node, const char *str, const KeyWord *kw,
-			 const KeySuffix *suf, const int *index, int ver, NUMDesc *Num)
+			 const KeySuffix *suf, const int *index, uint32 flags, NUMDesc *Num)
 {
 	FormatNode *n;
 
@@ -1305,7 +1308,7 @@ parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 		/*
 		 * Prefix
 		 */
-		if (ver == DCH_TYPE &&
+		if ((flags & DCH_FLAG) &&
 			(s = suff_search(str, suf, SUFFTYPE_PREFIX)) != NULL)
 		{
 			suffix |= s->id;
@@ -1326,13 +1329,13 @@ parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 			/*
 			 * NUM version: Prepare global NUMDesc struct
 			 */
-			if (ver == NUM_TYPE)
+			if (flags & NUM_FLAG)
 				NUMDesc_prepare(Num, n);
 
 			/*
 			 * Postfix
 			 */
-			if (ver == DCH_TYPE && *str &&
+			if ((flags & DCH_FLAG) && *str &&
 				(s = suff_search(str, suf, SUFFTYPE_POSTFIX)) != NULL)
 			{
 				n->suffix |= s->id;
@@ -1346,11 +1349,35 @@ parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 		{
 			int			chlen;
 
-			/*
-			 * Process double-quoted literal string, if any
-			 */
-			if (*str == '"')
+			if (flags & STD_FLAG)
+			{
+				/*
+				 * Standard mode, allow only following separators:
+				 * "-./,':; "
+				 */
+				if (strchr("-./,':; ", *str) == NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							 errmsg("invalid datetime format separator: \"%s\"",
+									pnstrdup(str, pg_mblen(str)))));
+
+				if (*str == ' ')
+					n->type = NODE_TYPE_SPACE;
+				else
+					n->type = NODE_TYPE_SEPARATOR;
+
+				n->character[0] = *str;
+				n->character[1] = '\0';
+				n->key = NULL;
+				n->suffix = 0;
+				n++;
+				str++;
+			}
+			else if (*str == '"')
 			{
+				/*
+				 * Process double-quoted literal string, if any
+				 */
 				str++;
 				while (*str)
 				{
@@ -1382,7 +1409,7 @@ parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 					str++;
 				chlen = pg_mblen(str);
 
-				if (ver == DCH_TYPE && is_separator_char(str))
+				if ((flags & DCH_FLAG) && is_separator_char(str))
 					n->type = NODE_TYPE_SEPARATOR;
 				else if (isspace((unsigned char) *str))
 					n->type = NODE_TYPE_SPACE;
@@ -3084,13 +3111,13 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
  * ----------
  */
 static void
-DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
+DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 {
 	FormatNode *n;
 	char	   *s;
 	int			len,
 				value;
-	bool		fx_mode = false;
+	bool		fx_mode = std;
 
 	/* number of extra skipped characters (more than given in format string) */
 	int			extra_skip = 0;
@@ -3113,7 +3140,23 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 
 		if (n->type == NODE_TYPE_SPACE || n->type == NODE_TYPE_SEPARATOR)
 		{
-			if (!fx_mode)
+			if (std)
+			{
+				/*
+				 * Standard mode requires strict matching between format string
+				 * separators/spaces and input string.
+				 */
+				Assert(n->character[0] && !n->character[1]);
+
+				if (*s == n->character[0])
+					s++;
+				else
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							 errmsg("unmatched format separator \"%c\"",
+									n->character[0])));
+			}
+			else if (!fx_mode)
 			{
 				/*
 				 * In non FX (fixed format) mode one format string space or
@@ -3465,6 +3508,27 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 			}
 		}
 	}
+
+	/*
+	 * Standard parsing mode doesn't allow unmatched format patterns or
+	 * trailing characters in the input string.
+	 */
+	if (std)
+	{
+		if (n->type != NODE_TYPE_END)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+					 errmsg("input string is too short for datetime format")));
+
+		while (*s != '\0' && isspace((unsigned char) *s))
+			s++;
+
+		if (*s != '\0')
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+					 errmsg("trailing characters remain in input string after "
+							"datetime format")));
+	}
 }
 
 /*
@@ -3487,7 +3551,7 @@ DCH_prevent_counter_overflow(void)
 
 /* select a DCHCacheEntry to hold the given format picture */
 static DCHCacheEntry *
-DCH_cache_getnew(const char *str)
+DCH_cache_getnew(const char *str, bool std)
 {
 	DCHCacheEntry *ent;
 
@@ -3537,6 +3601,7 @@ DCH_cache_getnew(const char *str)
 			MemoryContextAllocZero(TopMemoryContext, sizeof(DCHCacheEntry));
 		ent->valid = false;
 		StrNCpy(ent->str, str, DCH_CACHE_SIZE + 1);
+		ent->std = std;
 		ent->age = (++DCHCounter);
 		/* caller is expected to fill format, then set valid */
 		++n_DCHCache;
@@ -3546,7 +3611,7 @@ DCH_cache_getnew(const char *str)
 
 /* look for an existing DCHCacheEntry matching the given format picture */
 static DCHCacheEntry *
-DCH_cache_search(const char *str)
+DCH_cache_search(const char *str, bool std)
 {
 	/* Ensure we can advance DCHCounter below */
 	DCH_prevent_counter_overflow();
@@ -3555,7 +3620,7 @@ DCH_cache_search(const char *str)
 	{
 		DCHCacheEntry *ent = DCHCache[i];
 
-		if (ent->valid && strcmp(ent->str, str) == 0)
+		if (ent->valid && strcmp(ent->str, str) == 0 && ent->std == std)
 		{
 			ent->age = (++DCHCounter);
 			return ent;
@@ -3567,21 +3632,21 @@ DCH_cache_search(const char *str)
 
 /* Find or create a DCHCacheEntry for the given format picture */
 static DCHCacheEntry *
-DCH_cache_fetch(const char *str)
+DCH_cache_fetch(const char *str, bool std)
 {
 	DCHCacheEntry *ent;
 
-	if ((ent = DCH_cache_search(str)) == NULL)
+	if ((ent = DCH_cache_search(str, std)) == NULL)
 	{
 		/*
 		 * Not in the cache, must run parser and save a new format-picture to
 		 * the cache.  Do not mark the cache entry valid until parsing
 		 * succeeds.
 		 */
-		ent = DCH_cache_getnew(str);
+		ent = DCH_cache_getnew(str, std);
 
-		parse_format(ent->format, str, DCH_keywords,
-					 DCH_suff, DCH_index, DCH_TYPE, NULL);
+		parse_format(ent->format, str, DCH_keywords, DCH_suff, DCH_index,
+					 DCH_FLAG | (std ? STD_FLAG : 0), NULL);
 
 		ent->valid = true;
 	}
@@ -3626,14 +3691,14 @@ datetime_to_char_body(TmToChar *tmtc, text *fmt, bool is_interval, Oid collid)
 		format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode));
 
 		parse_format(format, fmt_str, DCH_keywords,
-					 DCH_suff, DCH_index, DCH_TYPE, NULL);
+					 DCH_suff, DCH_index, DCH_FLAG, NULL);
 	}
 	else
 	{
 		/*
 		 * Use cache buffers
 		 */
-		DCHCacheEntry *ent = DCH_cache_fetch(fmt_str);
+		DCHCacheEntry *ent = DCH_cache_fetch(fmt_str, false);
 
 		incache = true;
 		format = ent->format;
@@ -3775,7 +3840,7 @@ to_timestamp(PG_FUNCTION_ARGS)
 	fsec_t		fsec;
 	int			fprec;
 
-	do_to_timestamp(date_txt, fmt, &tm, &fsec, &fprec);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec);
 
 	/* Use the specified time zone, if any. */
 	if (tm.tm_zone)
@@ -3814,7 +3879,7 @@ to_date(PG_FUNCTION_ARGS)
 	struct pg_tm tm;
 	fsec_t		fsec;
 
-	do_to_timestamp(date_txt, fmt, &tm, &fsec, NULL);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL);
 
 	/* Prevent overflow in Julian-day routines */
 	if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3849,7 +3914,7 @@ to_date(PG_FUNCTION_ARGS)
  * struct 'tm' and 'fsec'.
  */
 static void
-do_to_timestamp(text *date_txt, text *fmt,
+do_to_timestamp(text *date_txt, text *fmt, bool std,
 				struct pg_tm *tm, fsec_t *fsec, int *fprec)
 {
 	FormatNode *format;
@@ -3884,15 +3949,15 @@ do_to_timestamp(text *date_txt, text *fmt,
 
 			format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode));
 
-			parse_format(format, fmt_str, DCH_keywords,
-						 DCH_suff, DCH_index, DCH_TYPE, NULL);
+			parse_format(format, fmt_str, DCH_keywords, DCH_suff, DCH_index,
+						 DCH_FLAG | (std ? STD_FLAG : 0), NULL);
 		}
 		else
 		{
 			/*
 			 * Use cache buffers
 			 */
-			DCHCacheEntry *ent = DCH_cache_fetch(fmt_str);
+			DCHCacheEntry *ent = DCH_cache_fetch(fmt_str, std);
 
 			incache = true;
 			format = ent->format;
@@ -3903,7 +3968,7 @@ do_to_timestamp(text *date_txt, text *fmt,
 		/* dump_index(DCH_keywords, DCH_index); */
 #endif
 
-		DCH_from_char(format, date_str, &tmfc);
+		DCH_from_char(format, date_str, &tmfc, std);
 
 		pfree(fmt_str);
 
@@ -4272,7 +4337,7 @@ NUM_cache_fetch(const char *str)
 		zeroize_NUM(&ent->Num);
 
 		parse_format(ent->format, str, NUM_keywords,
-					 NULL, NUM_index, NUM_TYPE, &ent->Num);
+					 NULL, NUM_index, NUM_FLAG, &ent->Num);
 
 		ent->valid = true;
 	}
@@ -4304,7 +4369,7 @@ NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree)
 		zeroize_NUM(Num);
 
 		parse_format(format, str, NUM_keywords,
-					 NULL, NUM_index, NUM_TYPE, Num);
+					 NULL, NUM_index, NUM_FLAG, Num);
 	}
 	else
 	{
-- 
2.14.3

0003-Implement-parse_datetime-function-7.patchapplication/octet-stream; name=0003-Implement-parse_datetime-function-7.patchDownload
From 1873de08e3a120bb96c103009728ca7caeb61952 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Mon, 26 Aug 2019 05:34:19 +0300
Subject: [PATCH 3/6] Implement parse_datetime() function

This commit adds parse_datetime() function, which implements datetime
parsing with extended features demanded by upcoming jsonpath .datetime()
method:

 * Dynamic type identification based on template string,
 * Support for standard-conforming 'strict' mode,
 * Timezone offset is returned as separate value.

Extracted from original patch by Nikita Glukhov, Teodor Sigaev, Oleg Bartunov.
Revised by me.

Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
Discussion: https://postgr.es/m/CAPpHfdsZgYEra_PeCLGNoXOWYx6iU-S3wF8aX0ObQUcZU%2B4XTw%40mail.gmail.com
Author: Nikita Glukhov, Teodor Sigaev, Oleg Bartunov, Alexander Korotkov
Reviewed-by: Anastasia Lubennikova, Peter Eisentraut
---
 src/backend/utils/adt/date.c       |  11 +-
 src/backend/utils/adt/formatting.c | 293 ++++++++++++++++++++++++++++++++++++-
 src/include/utils/date.h           |   3 +
 src/include/utils/formatting.h     |   3 +
 4 files changed, 298 insertions(+), 12 deletions(-)

diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 4b1afb10f92..9e291b5c7bc 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -41,11 +41,6 @@
 #endif
 
 
-static int	tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
-static int	tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
-static void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
-
-
 /* common code for timetypmodin and timetztypmodin */
 static int32
 anytime_typmodin(bool istz, ArrayType *ta)
@@ -1203,7 +1198,7 @@ time_in(PG_FUNCTION_ARGS)
 /* tm2time()
  * Convert a tm structure to a time data type.
  */
-static int
+int
 tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result)
 {
 	*result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec)
@@ -1379,7 +1374,7 @@ time_scale(PG_FUNCTION_ARGS)
  * have a fundamental tie together but rather a coincidence of
  * implementation. - thomas
  */
-static void
+void
 AdjustTimeForTypmod(TimeADT *time, int32 typmod)
 {
 	static const int64 TimeScales[MAX_TIME_PRECISION + 1] = {
@@ -1957,7 +1952,7 @@ time_part(PG_FUNCTION_ARGS)
 /* tm2timetz()
  * Convert a tm structure to a time data type.
  */
-static int
+int
 tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result)
 {
 	result->time = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) *
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 50a261adac5..445c1e1e71a 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -1000,6 +1000,11 @@ typedef struct NUMProc
 			   *L_currency_symbol;
 } NUMProc;
 
+/* Return flags for DCH_from_char() */
+#define DCH_DATED	0x01
+#define DCH_TIMED	0x02
+#define DCH_ZONED	0x04
+
 /* ----------
  * Functions
  * ----------
@@ -1034,7 +1039,8 @@ static int	from_char_parse_int(int *dest, char **src, FormatNode *node);
 static int	seq_search(char *name, const char *const *array, int type, int max, int *len);
 static int	from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
 static void do_to_timestamp(text *date_txt, text *fmt, bool std,
-							struct pg_tm *tm, fsec_t *fsec, int *fprec);
+							struct pg_tm *tm, fsec_t *fsec, int *fprec,
+							uint32 *flags);
 static char *fill_str(char *str, int c, int max);
 static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
 static char *int_to_roman(int number);
@@ -3549,6 +3555,111 @@ DCH_prevent_counter_overflow(void)
 	}
 }
 
+/* Get mask of date/time/zone components present in format nodes. */
+static int
+DCH_datetime_type(FormatNode *node)
+{
+	FormatNode *n;
+	int			flags = 0;
+
+	for (n = node; n->type != NODE_TYPE_END; n++)
+	{
+		if (n->type != NODE_TYPE_ACTION)
+			continue;
+
+		switch (n->key->id)
+		{
+			case DCH_FX:
+				break;
+			case DCH_A_M:
+			case DCH_P_M:
+			case DCH_a_m:
+			case DCH_p_m:
+			case DCH_AM:
+			case DCH_PM:
+			case DCH_am:
+			case DCH_pm:
+			case DCH_HH:
+			case DCH_HH12:
+			case DCH_HH24:
+			case DCH_MI:
+			case DCH_SS:
+			case DCH_MS:		/* millisecond */
+			case DCH_US:		/* microsecond */
+			case DCH_FF1:
+			case DCH_FF2:
+			case DCH_FF3:
+			case DCH_FF4:
+			case DCH_FF5:
+			case DCH_FF6:
+			case DCH_SSSS:
+				flags |= DCH_TIMED;
+				break;
+			case DCH_tz:
+			case DCH_TZ:
+			case DCH_OF:
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("formatting field \"%s\" is only supported in to_char",
+								n->key->name)));
+				flags |= DCH_ZONED;
+				break;
+			case DCH_TZH:
+			case DCH_TZM:
+				flags |= DCH_ZONED;
+				break;
+			case DCH_A_D:
+			case DCH_B_C:
+			case DCH_a_d:
+			case DCH_b_c:
+			case DCH_AD:
+			case DCH_BC:
+			case DCH_ad:
+			case DCH_bc:
+			case DCH_MONTH:
+			case DCH_Month:
+			case DCH_month:
+			case DCH_MON:
+			case DCH_Mon:
+			case DCH_mon:
+			case DCH_MM:
+			case DCH_DAY:
+			case DCH_Day:
+			case DCH_day:
+			case DCH_DY:
+			case DCH_Dy:
+			case DCH_dy:
+			case DCH_DDD:
+			case DCH_IDDD:
+			case DCH_DD:
+			case DCH_D:
+			case DCH_ID:
+			case DCH_WW:
+			case DCH_Q:
+			case DCH_CC:
+			case DCH_Y_YYY:
+			case DCH_YYYY:
+			case DCH_IYYY:
+			case DCH_RRRR:
+			case DCH_YYY:
+			case DCH_IYY:
+			case DCH_YY:
+			case DCH_IY:
+			case DCH_RR:
+			case DCH_Y:
+			case DCH_I:
+			case DCH_RM:
+			case DCH_rm:
+			case DCH_W:
+			case DCH_J:
+				flags |= DCH_DATED;
+				break;
+		}
+	}
+
+	return flags;
+}
+
 /* select a DCHCacheEntry to hold the given format picture */
 static DCHCacheEntry *
 DCH_cache_getnew(const char *str, bool std)
@@ -3840,7 +3951,7 @@ to_timestamp(PG_FUNCTION_ARGS)
 	fsec_t		fsec;
 	int			fprec;
 
-	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL);
 
 	/* Use the specified time zone, if any. */
 	if (tm.tm_zone)
@@ -3879,7 +3990,7 @@ to_date(PG_FUNCTION_ARGS)
 	struct pg_tm tm;
 	fsec_t		fsec;
 
-	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL);
 
 	/* Prevent overflow in Julian-day routines */
 	if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3900,6 +4011,176 @@ to_date(PG_FUNCTION_ARGS)
 	PG_RETURN_DATEADT(result);
 }
 
+/*
+ * Convert the 'date_txt' input to a datetime type using argument 'fmt' as a format string.
+ * The actual data type (returned in 'typid', 'typmod') is determined by
+ * the presence of date/time/zone components in the format string.
+ *
+ * When timezone component is present, the corresponding offset is set to '*tz'.
+ */
+Datum
+parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
+			   int32 *typmod, int *tz)
+{
+	struct pg_tm tm;
+	fsec_t		fsec;
+	int			fprec = 0;
+	uint32		flags;
+
+	do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags);
+
+	*typmod = fprec ? fprec : -1;	/* fractional part precision */
+
+	if (flags & DCH_DATED)
+	{
+		if (flags & DCH_TIMED)
+		{
+			if (flags & DCH_ZONED)
+			{
+				TimestampTz result;
+
+				if (tm.tm_zone)
+				{
+					int			dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
+
+					if (dterr)
+						DateTimeParseError(dterr, text_to_cstring(date_txt), "timestamptz");
+				}
+				else
+				{
+					/*
+					 * Time zone is present in format string, but not in input
+					 * string.  Assuming do_to_timestamp() triggers no error
+					 * this should be possible only in non-strict case.
+					 */
+					Assert(!strict);
+
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							 errmsg("missing time zone in input string for type timestamptz")));
+				}
+
+				if (tm2timestamp(&tm, fsec, tz, &result) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("timestamptz out of range")));
+
+				AdjustTimestampForTypmod(&result, *typmod);
+
+				*typid = TIMESTAMPTZOID;
+				return TimestampTzGetDatum(result);
+			}
+			else
+			{
+				Timestamp	result;
+
+				if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("timestamp out of range")));
+
+				AdjustTimestampForTypmod(&result, *typmod);
+
+				*typid = TIMESTAMPOID;
+				return TimestampGetDatum(result);
+			}
+		}
+		else
+		{
+			if (flags & DCH_ZONED)
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+						 errmsg("datetime format is zoned but not timed")));
+			}
+			else
+			{
+				DateADT		result;
+
+				/* Prevent overflow in Julian-day routines */
+				if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("date out of range: \"%s\"",
+									text_to_cstring(date_txt))));
+
+				result = 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(result))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("date out of range: \"%s\"",
+									text_to_cstring(date_txt))));
+
+				*typid = DATEOID;
+				return DateADTGetDatum(result);
+			}
+		}
+	}
+	else if (flags & DCH_TIMED)
+	{
+		if (flags & DCH_ZONED)
+		{
+			TimeTzADT  *result = palloc(sizeof(TimeTzADT));
+
+			if (tm.tm_zone)
+			{
+				int			dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
+
+				if (dterr)
+					DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz");
+			}
+			else
+			{
+				/*
+				 * Time zone is present in format string, but not in input
+				 * string.  Assuming do_to_timestamp() triggers no error this
+				 * should be possible only in non-strict case.
+				 */
+				Assert(!strict);
+
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+						 errmsg("missing time zone in input string for type timetz")));
+			}
+
+			if (tm2timetz(&tm, fsec, *tz, result) != 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("timetz out of range")));
+
+			AdjustTimeForTypmod(&result->time, *typmod);
+
+			*typid = TIMETZOID;
+			return TimeTzADTPGetDatum(result);
+		}
+		else
+		{
+			TimeADT		result;
+
+			if (tm2time(&tm, fsec, &result) != 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("time out of range")));
+
+			AdjustTimeForTypmod(&result, *typmod);
+
+			*typid = TIMEOID;
+			return TimeADTGetDatum(result);
+		}
+	}
+	else
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+				 errmsg("datetime format is not dated and not timed")));
+	}
+
+	return (Datum) 0;
+}
+
 /*
  * do_to_timestamp: shared code for to_timestamp and to_date
  *
@@ -3915,7 +4196,8 @@ to_date(PG_FUNCTION_ARGS)
  */
 static void
 do_to_timestamp(text *date_txt, text *fmt, bool std,
-				struct pg_tm *tm, fsec_t *fsec, int *fprec)
+				struct pg_tm *tm, fsec_t *fsec, int *fprec,
+				uint32 *flags)
 {
 	FormatNode *format;
 	TmFromChar	tmfc;
@@ -3972,6 +4254,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 
 		pfree(fmt_str);
 
+		if (flags)
+			*flags = DCH_datetime_type(format);
+
 		if (!incache)
 			pfree(format);
 	}
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index bec129aff1c..bd15bfa5bb0 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -76,5 +76,8 @@ extern TimeTzADT *GetSQLCurrentTime(int32 typmod);
 extern TimeADT GetSQLLocalTime(int32 typmod);
 extern int	time2tm(TimeADT time, struct pg_tm *tm, fsec_t *fsec);
 extern int	timetz2tm(TimeTzADT *time, struct pg_tm *tm, fsec_t *fsec, int *tzp);
+extern int	tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
+extern int	tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
+extern void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
 
 #endif							/* DATE_H */
diff --git a/src/include/utils/formatting.h b/src/include/utils/formatting.h
index 0117144779e..beeaf10c332 100644
--- a/src/include/utils/formatting.h
+++ b/src/include/utils/formatting.h
@@ -26,4 +26,7 @@ extern char *asc_tolower(const char *buff, size_t nbytes);
 extern char *asc_toupper(const char *buff, size_t nbytes);
 extern char *asc_initcap(const char *buff, size_t nbytes);
 
+extern Datum parse_datetime(text *date_txt, text *fmt, bool std,
+							Oid *typid, int32 *typmod, int *tz);
+
 #endif
-- 
2.14.3

0001-Introduce-RRRR-and-RR-revise-YYY-YY-and-Y-datetime-7.patchapplication/octet-stream; name=0001-Introduce-RRRR-and-RR-revise-YYY-YY-and-Y-datetime-7.patchDownload
From 22f3a93247f83dd7d9c950c12b581214cb56682f Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Tue, 20 Aug 2019 07:57:11 +0300
Subject: [PATCH 1/6] Introduce RRRR and RR, revise YYY, YY and Y datetime
 format patterns

SQL Standard 2016 defines RR and RRRR format patterns and interprets YYY, YY
and Y format patterns in the different way than current implementation of
to_date()/to_timestamp() does.

According to standard YYY, YY and Y should get higher digits from current year.
Our current implementation gets higher digits so that the result is closest
to 2020.  Now, we get just higher digits from 2020 instead.  We use 2020 instead
of current year in order to keep our conversion immutable.

According to standard RR behavior is implementation-defined and should select
marching 4-digit year in the interval [CY - 100; CY + 100], where CY is current
year.  We use logic, which was previously used in YY, select the matching year
closest to 2020.

The change is made in favor of upcoming jsonpath .datetime() method.  Standard
applies to jsonpath .datetime() method and CAST (... FORMAT ...) SQL clause.
But in order to keep our behavior consistent, we apply this change to
to_date()/to_timestamp() too.

Discussion: https://postgr.es/m/CAPpHfdsZgYEra_PeCLGNoXOWYx6iU-S3wF8aX0ObQUcZU%2B4XTw%40mail.gmail.com
Author: Alexander Korotkov
---
 doc/src/sgml/func.sgml                 | 32 +++++++++++++++---
 src/backend/utils/adt/formatting.c     | 59 ++++++++++++++++++++++++++--------
 src/test/regress/expected/horology.out | 29 +++++++++++++----
 src/test/regress/sql/horology.sql      | 12 +++++--
 4 files changed, 103 insertions(+), 29 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 2b4fe0cb593..e5c6fe89c2f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -6193,7 +6193,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
         <entry>year (4 or more digits) with comma</entry>
        </row>
        <row>
-        <entry><literal>YYYY</literal></entry>
+        <entry><literal>YYYY</literal>, <literal>RRRR</literal></entry>
         <entry>year (4 or more digits)</entry>
        </row>
        <row>
@@ -6224,6 +6224,10 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
         <entry><literal>I</literal></entry>
         <entry>last digit of ISO 8601 week-numbering year</entry>
        </row>
+       <row>
+        <entry><literal>RR</literal></entry>
+        <entry>last 2 digits of rounded year</entry>
+       </row>
        <row>
         <entry><literal>BC</literal>, <literal>bc</literal>,
         <literal>AD</literal> or <literal>ad</literal></entry>
@@ -6544,10 +6548,18 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
      <listitem>
       <para>
        In <function>to_timestamp</function> and <function>to_date</function>,
-       if the year format specification is less than four digits, e.g.
-       <literal>YYY</literal>, and the supplied year is less than four digits,
-       the year will be adjusted to be nearest to the year 2020, e.g.
-       <literal>95</literal> becomes 1995.
+       when using <literal>YYY</literal>, <literal>YY</literal> or
+       <literal>Y</literal> fields, then higher year digits are taken from
+       2020, e.g. <literal>7</literal> becomes 2027.
+      </para>
+     </listitem>
+
+     <listitem>
+      <para>
+       In <function>to_timestamp</function> and <function>to_date</function>,
+       when using <literal>RR</literal> field, the supplied year will be
+       adjusted to be nearest to the year 2020, e.g. <literal>95</literal>
+       becomes 1995.
       </para>
      </listitem>
 
@@ -6678,6 +6690,16 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
       </para>
      </listitem>
 
+     <listitem>
+      <para>
+        <function>to_char(interval)</function> formats <literal>HH</literal> and
+        <literal>HH12</literal> as shown on a 12-hour clock, for example zero hours
+        and 36 hours both output as <literal>12</literal>, while <literal>HH24</literal>
+        outputs the full hour value, which can exceed 23 in
+        an <type>interval</type> value.
+      </para>
+     </listitem>
+
     </itemizedlist>
    </para>
 
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 053affa5cec..65f0439ae9f 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -626,6 +626,8 @@ typedef enum
 	DCH_PM,
 	DCH_Q,
 	DCH_RM,
+	DCH_RRRR,
+	DCH_RR,
 	DCH_SSSSS,
 	DCH_SSSS,
 	DCH_SS,
@@ -679,6 +681,8 @@ typedef enum
 	DCH_pm,
 	DCH_q,
 	DCH_rm,
+	DCH_rrrr,
+	DCH_rr,
 	DCH_sssss,
 	DCH_ssss,
 	DCH_ss,
@@ -789,6 +793,8 @@ static const KeyWord DCH_keywords[] = {
 	{"PM", 2, DCH_PM, false, FROM_CHAR_DATE_NONE},
 	{"Q", 1, DCH_Q, true, FROM_CHAR_DATE_NONE}, /* Q */
 	{"RM", 2, DCH_RM, false, FROM_CHAR_DATE_GREGORIAN}, /* R */
+	{"RRRR", 4, DCH_RRRR, true, FROM_CHAR_DATE_GREGORIAN},
+	{"RR", 2, DCH_RR, true, FROM_CHAR_DATE_GREGORIAN},
 	{"SSSSS", 5, DCH_SSSS, true, FROM_CHAR_DATE_NONE},	/* S */
 	{"SSSS", 4, DCH_SSSS, true, FROM_CHAR_DATE_NONE},
 	{"SS", 2, DCH_SS, true, FROM_CHAR_DATE_NONE},
@@ -842,6 +848,8 @@ static const KeyWord DCH_keywords[] = {
 	{"pm", 2, DCH_pm, false, FROM_CHAR_DATE_NONE},
 	{"q", 1, DCH_Q, true, FROM_CHAR_DATE_NONE}, /* q */
 	{"rm", 2, DCH_rm, false, FROM_CHAR_DATE_GREGORIAN}, /* r */
+	{"rrrr", 4, DCH_RRRR, true, FROM_CHAR_DATE_GREGORIAN},
+	{"rr", 2, DCH_RR, true, FROM_CHAR_DATE_GREGORIAN},
 	{"sssss", 5, DCH_SSSS, true, FROM_CHAR_DATE_NONE},	/* s */
 	{"ssss", 4, DCH_SSSS, true, FROM_CHAR_DATE_NONE},
 	{"ss", 2, DCH_SS, true, FROM_CHAR_DATE_NONE},
@@ -1013,7 +1021,8 @@ static void dump_node(FormatNode *node, int max);
 
 static const char *get_th(char *num, int type);
 static char *str_numth(char *dest, char *num, int type);
-static int	adjust_partial_year_to_2020(int year);
+static int	adjust_partial_year_to_2020(int year, int ndigits);
+static int	adjust_partial_round_year_to_2020(int year);
 static int	strspace_len(char *str);
 static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode);
 static void from_char_set_int(int *dest, const int value, const FormatNode *node);
@@ -2159,8 +2168,27 @@ is_next_separator(FormatNode *n)
 }
 
 
+/*
+ * Get lower 'ndigits' from 'year' while take other higher digits from
+ * 2020.
+ */
 static int
-adjust_partial_year_to_2020(int year)
+adjust_partial_year_to_2020(int year, int ndigits)
+{
+	if (ndigits == 1)
+		return year + 2020;
+	else if (ndigits == 2 || ndigits == 3)
+		return year + 2000;
+	else
+		return year;
+}
+
+
+/*
+ * Get closest to 2020 4-digit year, which corresponds to given 2-digits year.
+ */
+static int
+adjust_partial_round_year_to_2020(int year)
 {
 	/*
 	 * Adjust all dates toward 2020; this is effectively what happens when we
@@ -2172,12 +2200,6 @@ adjust_partial_year_to_2020(int year)
 	/* Force 70-99 into the 1900's */
 	else if (year < 100)
 		return year + 1900;
-	/* Force 100-519 into the 2000's */
-	else if (year < 520)
-		return year + 2000;
-	/* Force 520-999 into the 1000's */
-	else if (year < 1000)
-		return year + 1000;
 	else
 		return year;
 }
@@ -2961,6 +2983,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 					str_numth(s, s, S_TH_TYPE(n->suffix));
 				s += strlen(s);
 				break;
+			case DCH_RRRR:
 			case DCH_YYYY:
 			case DCH_IYYY:
 				sprintf(s, "%0*d",
@@ -2991,6 +3014,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 					str_numth(s, s, S_TH_TYPE(n->suffix));
 				s += strlen(s);
 				break;
+			case DCH_RR:
 			case DCH_YY:
 			case DCH_IY:
 				sprintf(s, "%0*d",
@@ -3376,6 +3400,7 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 					SKIP_THth(s, n->suffix);
 				}
 				break;
+			case DCH_RRRR:
 			case DCH_YYYY:
 			case DCH_IYYY:
 				from_char_parse_int(&out->year, &s, n);
@@ -3384,22 +3409,28 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 				break;
 			case DCH_YYY:
 			case DCH_IYY:
-				if (from_char_parse_int(&out->year, &s, n) < 4)
-					out->year = adjust_partial_year_to_2020(out->year);
+				len = from_char_parse_int(&out->year, &s, n);
+				out->year = adjust_partial_year_to_2020(out->year, len);
 				out->yysz = 3;
 				SKIP_THth(s, n->suffix);
 				break;
+			case DCH_RR:
+				from_char_parse_int(&out->year, &s, n);
+				out->year = adjust_partial_round_year_to_2020(out->year);
+				out->yysz = 2;
+				SKIP_THth(s, n->suffix);
+				break;
 			case DCH_YY:
 			case DCH_IY:
-				if (from_char_parse_int(&out->year, &s, n) < 4)
-					out->year = adjust_partial_year_to_2020(out->year);
+				len = from_char_parse_int(&out->year, &s, n);
+				out->year = adjust_partial_year_to_2020(out->year, len);
 				out->yysz = 2;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Y:
 			case DCH_I:
-				if (from_char_parse_int(&out->year, &s, n) < 4)
-					out->year = adjust_partial_year_to_2020(out->year);
+				len = from_char_parse_int(&out->year, &s, n);
+				out->year = adjust_partial_year_to_2020(out->year, len);
 				out->yysz = 1;
 				SKIP_THth(s, n->suffix);
 				break;
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index 6b53876e062..f68882cc173 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -2597,7 +2597,7 @@ SELECT to_timestamp('1,582nd VIII 21', 'Y,YYYth FMRM DD');
 (1 row)
 
 SELECT to_timestamp('15 "text between quote marks" 98 54 45',
-                    E'HH24 "\\"text between quote marks\\"" YY MI SS');
+                    E'HH24 "\\"text between quote marks\\"" RR MI SS');
          to_timestamp         
 ------------------------------
  Thu Jan 01 15:54:45 1998 PST
@@ -2618,19 +2618,22 @@ SELECT to_timestamp('2000January09Sunday', 'YYYYFMMonthDDFMDay');
 SELECT to_timestamp('97/Feb/16', 'YYMonDD');
 ERROR:  invalid value "/Fe" for "Mon"
 DETAIL:  The given value did not match any of the allowed values for this field.
+SELECT to_timestamp('97/Feb/16', 'RRMonDD');
+ERROR:  invalid value "/Fe" for "Mon"
+DETAIL:  The given value did not match any of the allowed values for this field.
 SELECT to_timestamp('97/Feb/16', 'YY:Mon:DD');
          to_timestamp         
 ------------------------------
- Sun Feb 16 00:00:00 1997 PST
+ Sat Feb 16 00:00:00 2097 PST
 (1 row)
 
-SELECT to_timestamp('97/Feb/16', 'FXYY:Mon:DD');
+SELECT to_timestamp('97/Feb/16', 'FXRR:Mon:DD');
          to_timestamp         
 ------------------------------
  Sun Feb 16 00:00:00 1997 PST
 (1 row)
 
-SELECT to_timestamp('97/Feb/16', 'FXYY/Mon/DD');
+SELECT to_timestamp('97/Feb/16', 'FXRR/Mon/DD');
          to_timestamp         
 ------------------------------
  Sun Feb 16 00:00:00 1997 PST
@@ -2642,6 +2645,12 @@ SELECT to_timestamp('19971116', 'YYYYMMDD');
  Sun Nov 16 00:00:00 1997 PST
 (1 row)
 
+SELECT to_timestamp('19971116', 'RRRRMMDD');
+         to_timestamp         
+------------------------------
+ Sun Nov 16 00:00:00 1997 PST
+(1 row)
+
 SELECT to_timestamp('20000-1116', 'YYYY-MMDD');
          to_timestamp          
 -------------------------------
@@ -2663,11 +2672,17 @@ SELECT to_timestamp('1997 BC 11 16', 'YYYY BC MM DD');
 SELECT to_timestamp('9-1116', 'Y-MMDD');
          to_timestamp         
 ------------------------------
- Mon Nov 16 00:00:00 2009 PST
+ Fri Nov 16 00:00:00 2029 PST
 (1 row)
 
 SELECT to_timestamp('95-1116', 'YY-MMDD');
          to_timestamp         
+------------------------------
+ Wed Nov 16 00:00:00 2095 PST
+(1 row)
+
+SELECT to_timestamp('95-1116', 'RR-MMDD');
+         to_timestamp         
 ------------------------------
  Thu Nov 16 00:00:00 1995 PST
 (1 row)
@@ -2675,7 +2690,7 @@ SELECT to_timestamp('95-1116', 'YY-MMDD');
 SELECT to_timestamp('995-1116', 'YYY-MMDD');
          to_timestamp         
 ------------------------------
- Thu Nov 16 00:00:00 1995 PST
+ Mon Nov 16 00:00:00 2995 PST
 (1 row)
 
 SELECT to_timestamp('2005426', 'YYYYWWD');
@@ -2711,7 +2726,7 @@ SELECT to_timestamp('05527', 'IYIWID');
 SELECT to_timestamp('5527', 'IIWID');
          to_timestamp         
 ------------------------------
- Sun Jan 01 00:00:00 2006 PST
+ Sun Dec 28 00:00:00 2025 PST
 (1 row)
 
 SELECT to_timestamp('2005364', 'IYYYIDDD');
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7a9da1e954..e35cb448508 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -344,7 +344,7 @@ SELECT to_timestamp('My birthday-> Year: 1976, Month: May, Day: 16',
 SELECT to_timestamp('1,582nd VIII 21', 'Y,YYYth FMRM DD');
 
 SELECT to_timestamp('15 "text between quote marks" 98 54 45',
-                    E'HH24 "\\"text between quote marks\\"" YY MI SS');
+                    E'HH24 "\\"text between quote marks\\"" RR MI SS');
 
 SELECT to_timestamp('05121445482000', 'MMDDHH24MISSYYYY');
 
@@ -352,14 +352,18 @@ SELECT to_timestamp('2000January09Sunday', 'YYYYFMMonthDDFMDay');
 
 SELECT to_timestamp('97/Feb/16', 'YYMonDD');
 
+SELECT to_timestamp('97/Feb/16', 'RRMonDD');
+
 SELECT to_timestamp('97/Feb/16', 'YY:Mon:DD');
 
-SELECT to_timestamp('97/Feb/16', 'FXYY:Mon:DD');
+SELECT to_timestamp('97/Feb/16', 'FXRR:Mon:DD');
 
-SELECT to_timestamp('97/Feb/16', 'FXYY/Mon/DD');
+SELECT to_timestamp('97/Feb/16', 'FXRR/Mon/DD');
 
 SELECT to_timestamp('19971116', 'YYYYMMDD');
 
+SELECT to_timestamp('19971116', 'RRRRMMDD');
+
 SELECT to_timestamp('20000-1116', 'YYYY-MMDD');
 
 SELECT to_timestamp('1997 AD 11 16', 'YYYY BC MM DD');
@@ -369,6 +373,8 @@ SELECT to_timestamp('9-1116', 'Y-MMDD');
 
 SELECT to_timestamp('95-1116', 'YY-MMDD');
 
+SELECT to_timestamp('95-1116', 'RR-MMDD');
+
 SELECT to_timestamp('995-1116', 'YYY-MMDD');
 
 SELECT to_timestamp('2005426', 'YYYYWWD');
-- 
2.14.3

0004-Error-suppression-support-for-upcoming-jsonpath-.d-7.patchapplication/octet-stream; name=0004-Error-suppression-support-for-upcoming-jsonpath-.d-7.patchDownload
From 731f2aa55b565820676094cd023bf6bf4683c347 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Sun, 21 Jul 2019 01:14:48 +0300
Subject: [PATCH 4/6] Error suppression support for upcoming jsonpath
 .datetime() method

Add support of error suppression in some date and time manipulation functions
as it's required for jsonpath .datetime() method support.  This commit doesn't
use PG_TRY()/PG_CATCH() in order to implement that.  Instead, it provides
internal versions of date and time functions used, which support error
suppression.

Discussion: https://postgr.es/m/CAPpHfdsZgYEra_PeCLGNoXOWYx6iU-S3wF8aX0ObQUcZU%2B4XTw%40mail.gmail.com
Author: Alexander Korotkov, Nikita Glukhov
Reviewed-by: Anastasia Lubennikova, Peter Eisentraut
---
 src/backend/utils/adt/date.c       |  86 ++++--
 src/backend/utils/adt/formatting.c | 537 ++++++++++++++++++++++++-------------
 src/backend/utils/adt/timestamp.c  |  66 +++--
 src/include/utils/date.h           |   2 +
 src/include/utils/datetime.h       |   2 +
 src/include/utils/formatting.h     |   3 +-
 src/include/utils/timestamp.h      |   3 +
 7 files changed, 478 insertions(+), 221 deletions(-)

diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 9e291b5c7bc..fa50d79c05d 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -550,13 +550,15 @@ date_mii(PG_FUNCTION_ARGS)
 	PG_RETURN_DATEADT(result);
 }
 
+
 /*
- * Internal routines for promoting date to timestamp and timestamp with
- * time zone
+ * Promote date to timestamp.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero is returned.
  */
-
-static Timestamp
-date2timestamp(DateADT dateVal)
+Timestamp
+date2timestamp_opt_error(DateADT dateVal, bool *have_error)
 {
 	Timestamp	result;
 
@@ -572,9 +574,19 @@ date2timestamp(DateADT dateVal)
 		 * boundary need be checked for overflow.
 		 */
 		if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("date out of range for timestamp")));
+		{
+			if (have_error)
+			{
+				*have_error = true;
+				return (Timestamp) 0;
+			}
+			else
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("date out of range for timestamp")));
+			}
+		}
 
 		/* date is days since 2000, timestamp is microseconds since same... */
 		result = dateVal * USECS_PER_DAY;
@@ -583,8 +595,23 @@ date2timestamp(DateADT dateVal)
 	return result;
 }
 
+/*
+ * Single-argument version of date2timestamp_opt_error().
+ */
 static TimestampTz
-date2timestamptz(DateADT dateVal)
+date2timestamp(DateADT dateVal)
+{
+	return date2timestamp_opt_error(dateVal, NULL);
+}
+
+/*
+ * Promote date to timestamp with time zone.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero is returned.
+ */
+TimestampTz
+date2timestamptz_opt_error(DateADT dateVal, bool *have_error)
 {
 	TimestampTz result;
 	struct pg_tm tt,
@@ -603,9 +630,19 @@ date2timestamptz(DateADT dateVal)
 		 * boundary need be checked for overflow.
 		 */
 		if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("date out of range for timestamp")));
+		{
+			if (have_error)
+			{
+				*have_error = true;
+				return (TimestampTz) 0;
+			}
+			else
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("date out of range for timestamp")));
+			}
+		}
 
 		j2date(dateVal + POSTGRES_EPOCH_JDATE,
 			   &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday));
@@ -621,14 +658,33 @@ date2timestamptz(DateADT dateVal)
 		 * of time zone, check for allowed timestamp range after adding tz.
 		 */
 		if (!IS_VALID_TIMESTAMP(result))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("date out of range for timestamp")));
+		{
+			if (have_error)
+			{
+				*have_error = true;
+				return (TimestampTz) 0;
+			}
+			else
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("date out of range for timestamp")));
+			}
+		}
 	}
 
 	return result;
 }
 
+/*
+ * Single-argument version of date2timestamptz_opt_error().
+ */
+static TimestampTz
+date2timestamptz(DateADT dateVal)
+{
+	return date2timestamptz_opt_error(dateVal, NULL);
+}
+
 /*
  * date2timestamp_no_overflow
  *
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 445c1e1e71a..636830c2809 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -98,6 +98,43 @@
 #include "utils/numeric.h"
 #include "utils/pg_locale.h"
 
+/* ----------
+ * Convenience macros for error handling
+ * ----------
+ *
+ * Two macros below help to handle errors in functions that take
+ * 'bool *have_error' argument.  When this argument is not NULL, it's expected
+ * that function will suppress ereports when possible.  Instead it should
+ * return some default value and set *have_error flag.
+ *
+ * RETURN_ERROR() macro intended to wrap ereport() calls.  When have_error
+ * function argument is not NULL, then instead of ereport'ing we set
+ * *have_error flag and go to on_error label.  It's supposed that jump
+ * resources will be freed and some 'default' value returned.
+ *
+ * CHECK_ERROR() jumps on_error label when *have_error flag is defined and set.
+ * It's supposed to be used for immediate exit from the function on error
+ * after call of another function with 'bool *have_error' argument.
+ */
+#define RETURN_ERROR(throw_error) \
+do { \
+	if (have_error) \
+	{ \
+		*have_error = true; \
+		goto on_error; \
+	} \
+	else \
+	{ \
+		throw_error; \
+	} \
+} while (0)
+
+#define CHECK_ERROR \
+do { \
+	if (have_error && *have_error) \
+		goto on_error; \
+} while (0)
+
 /* ----------
  * Routines flags
  * ----------
@@ -1020,7 +1057,7 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 static void DCH_to_char(FormatNode *node, bool is_interval,
 						TmToChar *in, char *out, Oid collid);
 static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
-						  bool std);
+						  bool std, bool *have_error);
 
 #ifdef DEBUG_TO_FROM_CHAR
 static void dump_index(const KeyWord *k, const int *index);
@@ -1032,15 +1069,21 @@ static char *str_numth(char *dest, char *num, int type);
 static int	adjust_partial_year_to_2020(int year, int ndigits);
 static int	adjust_partial_round_year_to_2020(int year);
 static int	strspace_len(char *str);
-static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode);
-static void from_char_set_int(int *dest, const int value, const FormatNode *node);
-static int	from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node);
-static int	from_char_parse_int(int *dest, char **src, FormatNode *node);
+static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode,
+							   bool *have_error);
+static void from_char_set_int(int *dest, const int value, const FormatNode *node,
+							  bool *have_error);
+static int	from_char_parse_int_len(int *dest, char **src, const int len,
+									FormatNode *node, bool *have_error);
+static int	from_char_parse_int(int *dest, char **src, FormatNode *node,
+								bool *have_error);
 static int	seq_search(char *name, const char *const *array, int type, int max, int *len);
-static int	from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
+static int	from_char_seq_search(int *dest, char **src,
+								 const char *const *array, int type, int max,
+								 FormatNode *node, bool *have_error);
 static void do_to_timestamp(text *date_txt, text *fmt, bool std,
 							struct pg_tm *tm, fsec_t *fsec, int *fprec,
-							uint32 *flags);
+							uint32 *flags, bool *have_error);
 static char *fill_str(char *str, int c, int max);
 static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
 static char *int_to_roman(int number);
@@ -2256,21 +2299,26 @@ strspace_len(char *str)
  *
  * Puke if the date mode has already been set, and the caller attempts to set
  * it to a conflicting mode.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
-from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode)
+from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode, bool *have_error)
 {
 	if (mode != FROM_CHAR_DATE_NONE)
 	{
 		if (tmfc->mode == FROM_CHAR_DATE_NONE)
 			tmfc->mode = mode;
 		else if (tmfc->mode != mode)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("invalid combination of date conventions"),
-					 errhint("Do not mix Gregorian and ISO week date "
-							 "conventions in a formatting template.")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("invalid combination of date conventions"),
+								  errhint("Do not mix Gregorian and ISO week date "
+										  "conventions in a formatting template."))));
 	}
+
+on_error:
+	return;
 }
 
 /*
@@ -2278,18 +2326,25 @@ from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode)
  *
  * Puke if the destination integer has previously been set to some other
  * non-zero value.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
-from_char_set_int(int *dest, const int value, const FormatNode *node)
+from_char_set_int(int *dest, const int value, const FormatNode *node,
+				  bool *have_error)
 {
 	if (*dest != 0 && *dest != value)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("conflicting values for \"%s\" field in formatting string",
-						node->key->name),
-				 errdetail("This value contradicts a previous setting for "
-						   "the same field type.")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("conflicting values for \"%s\" field in "
+									 "formatting string",
+									 node->key->name),
+							  errdetail("This value contradicts a previous setting "
+										"for the same field type."))));
 	*dest = value;
+
+on_error:
+	return;
 }
 
 /*
@@ -2311,9 +2366,13 @@ from_char_set_int(int *dest, const int value, const FormatNode *node)
  * Note that from_char_parse_int() provides a more convenient wrapper where
  * the length of the field is the same as the length of the format keyword (as
  * with DD and MI).
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and -1 is returned.
  */
 static int
-from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
+from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node,
+						bool *have_error)
 {
 	long		result;
 	char		copy[DCH_MAX_ITEM_SIZ + 1];
@@ -2346,51 +2405,60 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
 		char	   *last;
 
 		if (used < len)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("source string too short for \"%s\" formatting field",
-							node->key->name),
-					 errdetail("Field requires %d characters, but only %d "
-							   "remain.",
-							   len, used),
-					 errhint("If your source string is not fixed-width, try "
-							 "using the \"FM\" modifier.")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("source string too short for \"%s\" "
+										 "formatting field",
+										 node->key->name),
+								  errdetail("Field requires %d characters, "
+											"but only %d remain.",
+											len, used),
+								  errhint("If your source string is not fixed-width, "
+										  "try using the \"FM\" modifier."))));
 
 		errno = 0;
 		result = strtol(copy, &last, 10);
 		used = last - copy;
 
 		if (used > 0 && used < len)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("invalid value \"%s\" for \"%s\"",
-							copy, node->key->name),
-					 errdetail("Field requires %d characters, but only %d "
-							   "could be parsed.", len, used),
-					 errhint("If your source string is not fixed-width, try "
-							 "using the \"FM\" modifier.")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("invalid value \"%s\" for \"%s\"",
+										 copy, node->key->name),
+								  errdetail("Field requires %d characters, "
+											"but only %d could be parsed.",
+											len, used),
+								  errhint("If your source string is not fixed-width, "
+										  "try using the \"FM\" modifier."))));
 
 		*src += used;
 	}
 
 	if (*src == init)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("invalid value \"%s\" for \"%s\"",
-						copy, node->key->name),
-				 errdetail("Value must be an integer.")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("invalid value \"%s\" for \"%s\"",
+									 copy, node->key->name),
+							  errdetail("Value must be an integer."))));
 
 	if (errno == ERANGE || result < INT_MIN || result > INT_MAX)
-		ereport(ERROR,
-				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-				 errmsg("value for \"%s\" in source string is out of range",
-						node->key->name),
-				 errdetail("Value must be in the range %d to %d.",
-						   INT_MIN, INT_MAX)));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							  errmsg("value for \"%s\" in source string is out of range",
+									 node->key->name),
+							  errdetail("Value must be in the range %d to %d.",
+										INT_MIN, INT_MAX))));
 
 	if (dest != NULL)
-		from_char_set_int(dest, (int) result, node);
+	{
+		from_char_set_int(dest, (int) result, node, have_error);
+		CHECK_ERROR;
+	}
+
 	return *src - init;
+
+on_error:
+	return -1;
 }
 
 /*
@@ -2403,9 +2471,9 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
  * required length explicitly.
  */
 static int
-from_char_parse_int(int *dest, char **src, FormatNode *node)
+from_char_parse_int(int *dest, char **src, FormatNode *node, bool *have_error)
 {
-	return from_char_parse_int_len(dest, src, node->key->len, node);
+	return from_char_parse_int_len(dest, src, node->key->len, node, have_error);
 }
 
 /* ----------
@@ -2488,11 +2556,12 @@ seq_search(char *name, const char *const *array, int type, int max, int *len)
  * pointed to by 'dest', advance 'src' to the end of the part of the string
  * which matched, and return the number of characters consumed.
  *
- * If the string doesn't match, throw an error.
+ * If the string doesn't match, throw an error if 'have_error' is NULL,
+ * otherwise set '*have_error' and return -1.
  */
 static int
-from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max,
-					 FormatNode *node)
+from_char_seq_search(int *dest, char **src, const char *const *array, int type,
+					 int max, FormatNode *node, bool *have_error)
 {
 	int			len;
 
@@ -2504,15 +2573,18 @@ from_char_seq_search(int *dest, char **src, const char *const *array, int type,
 		Assert(max <= DCH_MAX_ITEM_SIZ);
 		strlcpy(copy, *src, max + 1);
 
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("invalid value \"%s\" for \"%s\"",
-						copy, node->key->name),
-				 errdetail("The given value did not match any of the allowed "
-						   "values for this field.")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("invalid value \"%s\" for \"%s\"",
+									 copy, node->key->name),
+							  errdetail("The given value did not match any of "
+										"the allowed values for this field."))));
 	}
 	*src += len;
 	return len;
+
+on_error:
+	return -1;
 }
 
 /* ----------
@@ -3114,10 +3186,13 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
  *
  * Note: we currently don't have any to_interval() function, so there
  * is no need here for INVALID_FOR_INTERVAL checks.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  * ----------
  */
 static void
-DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
+DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std,
+			  bool *have_error)
 {
 	FormatNode *n;
 	char	   *s;
@@ -3157,10 +3232,10 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 				if (*s == n->character[0])
 					s++;
 				else
-					ereport(ERROR,
-							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-							 errmsg("unmatched format separator \"%c\"",
-									n->character[0])));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+										  errmsg("unmatched format separator \"%c\"",
+												 n->character[0]))));
 			}
 			else if (!fx_mode)
 			{
@@ -3216,7 +3291,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 			continue;
 		}
 
-		from_char_set_mode(out, n->key->date_mode);
+		from_char_set_mode(out, n->key->date_mode, have_error);
+		CHECK_ERROR;
 
 		switch (n->key->id)
 		{
@@ -3228,8 +3304,10 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 			case DCH_a_m:
 			case DCH_p_m:
 				from_char_seq_search(&value, &s, ampm_strings_long,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->pm, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->pm, value % 2, n, have_error);
+				CHECK_ERROR;
 				out->clock = CLOCK_12_HOUR;
 				break;
 			case DCH_AM:
@@ -3237,30 +3315,37 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 			case DCH_am:
 			case DCH_pm:
 				from_char_seq_search(&value, &s, ampm_strings,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->pm, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->pm, value % 2, n, have_error);
+				CHECK_ERROR;
 				out->clock = CLOCK_12_HOUR;
 				break;
 			case DCH_HH:
 			case DCH_HH12:
-				from_char_parse_int_len(&out->hh, &s, 2, n);
+				from_char_parse_int_len(&out->hh, &s, 2, n, have_error);
+				CHECK_ERROR;
 				out->clock = CLOCK_12_HOUR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_HH24:
-				from_char_parse_int_len(&out->hh, &s, 2, n);
+				from_char_parse_int_len(&out->hh, &s, 2, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_MI:
-				from_char_parse_int(&out->mi, &s, n);
+				from_char_parse_int(&out->mi, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_SS:
-				from_char_parse_int(&out->ss, &s, n);
+				from_char_parse_int(&out->ss, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_MS:		/* millisecond */
-				len = from_char_parse_int_len(&out->ms, &s, 3, n);
+				len = from_char_parse_int_len(&out->ms, &s, 3, n, have_error);
+				CHECK_ERROR;
 
 				/*
 				 * 25 is 0.25 and 250 is 0.25 too; 025 is 0.025 and not 0.25
@@ -3281,7 +3366,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 			case DCH_US:		/* microsecond */
 				len = from_char_parse_int_len(&out->us, &s,
 											  n->key->id == DCH_US ? 6 :
-											  out->ff, n);
+											  out->ff, n, have_error);
+				CHECK_ERROR;
 
 				out->us *= len == 1 ? 100000 :
 					len == 2 ? 10000 :
@@ -3292,16 +3378,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_SSSS:
-				from_char_parse_int(&out->ssss, &s, n);
+				from_char_parse_int(&out->ssss, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_tz:
 			case DCH_TZ:
 			case DCH_OF:
-				ereport(ERROR,
-						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						 errmsg("formatting field \"%s\" is only supported in to_char",
-								n->key->name)));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									  errmsg("formatting field \"%s\" is only supported in to_char",
+											 n->key->name))));
+				CHECK_ERROR;
 				break;
 			case DCH_TZH:
 
@@ -3325,82 +3413,102 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 						out->tzsign = +1;
 				}
 
-				from_char_parse_int_len(&out->tzh, &s, 2, n);
+				from_char_parse_int_len(&out->tzh, &s, 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_TZM:
 				/* assign positive timezone sign if TZH was not seen before */
 				if (!out->tzsign)
 					out->tzsign = +1;
-				from_char_parse_int_len(&out->tzm, &s, 2, n);
+				from_char_parse_int_len(&out->tzm, &s, 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_A_D:
 			case DCH_B_C:
 			case DCH_a_d:
 			case DCH_b_c:
 				from_char_seq_search(&value, &s, adbc_strings_long,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->bc, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->bc, value % 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_AD:
 			case DCH_BC:
 			case DCH_ad:
 			case DCH_bc:
 				from_char_seq_search(&value, &s, adbc_strings,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->bc, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->bc, value % 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_MONTH:
 			case DCH_Month:
 			case DCH_month:
 				from_char_seq_search(&value, &s, months_full, ONE_UPPER,
-									 MAX_MONTH_LEN, n);
-				from_char_set_int(&out->mm, value + 1, n);
+									 MAX_MONTH_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, value + 1, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_MON:
 			case DCH_Mon:
 			case DCH_mon:
 				from_char_seq_search(&value, &s, months, ONE_UPPER,
-									 MAX_MON_LEN, n);
-				from_char_set_int(&out->mm, value + 1, n);
+									 MAX_MON_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, value + 1, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_MM:
-				from_char_parse_int(&out->mm, &s, n);
+				from_char_parse_int(&out->mm, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_DAY:
 			case DCH_Day:
 			case DCH_day:
 				from_char_seq_search(&value, &s, days, ONE_UPPER,
-									 MAX_DAY_LEN, n);
-				from_char_set_int(&out->d, value, n);
+									 MAX_DAY_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->d, value, n, have_error);
+				CHECK_ERROR;
 				out->d++;
 				break;
 			case DCH_DY:
 			case DCH_Dy:
 			case DCH_dy:
 				from_char_seq_search(&value, &s, days, ONE_UPPER,
-									 MAX_DY_LEN, n);
-				from_char_set_int(&out->d, value, n);
+									 MAX_DY_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->d, value, n, have_error);
+				CHECK_ERROR;
 				out->d++;
 				break;
 			case DCH_DDD:
-				from_char_parse_int(&out->ddd, &s, n);
+				from_char_parse_int(&out->ddd, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_IDDD:
-				from_char_parse_int_len(&out->ddd, &s, 3, n);
+				from_char_parse_int_len(&out->ddd, &s, 3, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_DD:
-				from_char_parse_int(&out->dd, &s, n);
+				from_char_parse_int(&out->dd, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_D:
-				from_char_parse_int(&out->d, &s, n);
+				from_char_parse_int(&out->d, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_ID:
-				from_char_parse_int_len(&out->d, &s, 1, n);
+				from_char_parse_int_len(&out->d, &s, 1, n, have_error);
+				CHECK_ERROR;
 				/* Shift numbering to match Gregorian where Sunday = 1 */
 				if (++out->d > 7)
 					out->d = 1;
@@ -3408,7 +3516,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 				break;
 			case DCH_WW:
 			case DCH_IW:
-				from_char_parse_int(&out->ww, &s, n);
+				from_char_parse_int(&out->ww, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Q:
@@ -3423,11 +3532,13 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 				 * We still parse the source string for an integer, but it
 				 * isn't stored anywhere in 'out'.
 				 */
-				from_char_parse_int((int *) NULL, &s, n);
+				from_char_parse_int((int *) NULL, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_CC:
-				from_char_parse_int(&out->cc, &s, n);
+				from_char_parse_int(&out->cc, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Y_YYY:
@@ -3439,11 +3550,12 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 
 					matched = sscanf(s, "%d,%03d%n", &millennia, &years, &nch);
 					if (matched < 2)
-						ereport(ERROR,
-								(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-								 errmsg("invalid input string for \"Y,YYY\"")));
+						RETURN_ERROR(ereport(ERROR,
+											 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+											  errmsg("invalid input string for \"Y,YYY\""))));
 					years += (millennia * 1000);
-					from_char_set_int(&out->year, years, n);
+					from_char_set_int(&out->year, years, n, have_error);
+					CHECK_ERROR;
 					out->yysz = 4;
 					s += nch;
 					SKIP_THth(s, n->suffix);
@@ -3452,53 +3564,66 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 			case DCH_RRRR:
 			case DCH_YYYY:
 			case DCH_IYYY:
-				from_char_parse_int(&out->year, &s, n);
+				from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
 				out->yysz = 4;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_YYY:
 			case DCH_IYY:
-				len = from_char_parse_int(&out->year, &s, n);
+				len = from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
 				out->year = adjust_partial_year_to_2020(out->year, len);
 				out->yysz = 3;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_RR:
-				from_char_parse_int(&out->year, &s, n);
+				from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
 				out->year = adjust_partial_round_year_to_2020(out->year);
 				out->yysz = 2;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_YY:
 			case DCH_IY:
-				len = from_char_parse_int(&out->year, &s, n);
+				len = from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
 				out->year = adjust_partial_year_to_2020(out->year, len);
 				out->yysz = 2;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Y:
 			case DCH_I:
-				len = from_char_parse_int(&out->year, &s, n);
+				len = from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
 				out->year = adjust_partial_year_to_2020(out->year, len);
 				out->yysz = 1;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_RM:
 				from_char_seq_search(&value, &s, rm_months_upper,
-									 ALL_UPPER, MAX_RM_LEN, n);
-				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n);
+									 ALL_UPPER, MAX_RM_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value,
+								  n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_rm:
 				from_char_seq_search(&value, &s, rm_months_lower,
-									 ALL_LOWER, MAX_RM_LEN, n);
-				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n);
+									 ALL_LOWER, MAX_RM_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value,
+								  n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_W:
-				from_char_parse_int(&out->w, &s, n);
+				from_char_parse_int(&out->w, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_J:
-				from_char_parse_int(&out->j, &s, n);
+				from_char_parse_int(&out->j, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 		}
@@ -3522,19 +3647,22 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 	if (std)
 	{
 		if (n->type != NODE_TYPE_END)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("input string is too short for datetime format")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("input string is too short for datetime format"))));
 
 		while (*s != '\0' && isspace((unsigned char) *s))
 			s++;
 
 		if (*s != '\0')
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("trailing characters remain in input string after "
-							"datetime format")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("trailing characters remain in input string "
+										 "after datetime format"))));
 	}
+
+on_error:
+	return;
 }
 
 /*
@@ -3555,9 +3683,13 @@ DCH_prevent_counter_overflow(void)
 	}
 }
 
-/* Get mask of date/time/zone components present in format nodes. */
+/*
+ * Get mask of date/time/zone components present in format nodes.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
+ */
 static int
-DCH_datetime_type(FormatNode *node)
+DCH_datetime_type(FormatNode *node, bool *have_error)
 {
 	FormatNode *n;
 	int			flags = 0;
@@ -3598,10 +3730,10 @@ DCH_datetime_type(FormatNode *node)
 			case DCH_tz:
 			case DCH_TZ:
 			case DCH_OF:
-				ereport(ERROR,
-						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						 errmsg("formatting field \"%s\" is only supported in to_char",
-								n->key->name)));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									  errmsg("formatting field \"%s\" is only supported in to_char",
+											 n->key->name))));
 				flags |= DCH_ZONED;
 				break;
 			case DCH_TZH:
@@ -3657,6 +3789,7 @@ DCH_datetime_type(FormatNode *node)
 		}
 	}
 
+on_error:
 	return flags;
 }
 
@@ -3951,7 +4084,7 @@ to_timestamp(PG_FUNCTION_ARGS)
 	fsec_t		fsec;
 	int			fprec;
 
-	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL, NULL);
 
 	/* Use the specified time zone, if any. */
 	if (tm.tm_zone)
@@ -3990,7 +4123,7 @@ to_date(PG_FUNCTION_ARGS)
 	struct pg_tm tm;
 	fsec_t		fsec;
 
-	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL, NULL);
 
 	/* Prevent overflow in Julian-day routines */
 	if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -4017,17 +4150,21 @@ to_date(PG_FUNCTION_ARGS)
  * the presence of date/time/zone components in the format string.
  *
  * When timezone component is present, the corresponding offset is set to '*tz'.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero value is returned.
  */
 Datum
 parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
-			   int32 *typmod, int *tz)
+			   int32 *typmod, int *tz, bool *have_error)
 {
 	struct pg_tm tm;
 	fsec_t		fsec;
 	int			fprec = 0;
 	uint32		flags;
 
-	do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags);
+	do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags, have_error);
+	CHECK_ERROR;
 
 	*typmod = fprec ? fprec : -1;	/* fractional part precision */
 
@@ -4055,15 +4192,15 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 					 */
 					Assert(!strict);
 
-					ereport(ERROR,
-							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-							 errmsg("missing time zone in input string for type timestamptz")));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+										  errmsg("missing time zone in input string for type timestamptz"))));
 				}
 
 				if (tm2timestamp(&tm, fsec, tz, &result) != 0)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("timestamptz out of range")));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("timestamptz out of range"))));
 
 				AdjustTimestampForTypmod(&result, *typmod);
 
@@ -4075,9 +4212,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 				Timestamp	result;
 
 				if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("timestamp out of range")));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("timestamp out of range"))));
 
 				AdjustTimestampForTypmod(&result, *typmod);
 
@@ -4089,9 +4226,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 		{
 			if (flags & DCH_ZONED)
 			{
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-						 errmsg("datetime format is zoned but not timed")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+									  errmsg("datetime format is zoned but not timed"))));
 			}
 			else
 			{
@@ -4099,20 +4236,20 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 
 				/* Prevent overflow in Julian-day routines */
 				if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("date out of range: \"%s\"",
-									text_to_cstring(date_txt))));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("date out of range: \"%s\"",
+												 text_to_cstring(date_txt)))));
 
 				result = 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(result))
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("date out of range: \"%s\"",
-									text_to_cstring(date_txt))));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("date out of range: \"%s\"",
+												 text_to_cstring(date_txt)))));
 
 				*typid = DATEOID;
 				return DateADTGetDatum(result);
@@ -4130,7 +4267,7 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 				int			dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
 
 				if (dterr)
-					DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz");
+					RETURN_ERROR(DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz"));
 			}
 			else
 			{
@@ -4141,15 +4278,15 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 				 */
 				Assert(!strict);
 
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-						 errmsg("missing time zone in input string for type timetz")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+									  errmsg("missing time zone in input string for type timetz"))));
 			}
 
 			if (tm2timetz(&tm, fsec, *tz, result) != 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-						 errmsg("timetz out of range")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+									  errmsg("timetz out of range"))));
 
 			AdjustTimeForTypmod(&result->time, *typmod);
 
@@ -4161,9 +4298,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 			TimeADT		result;
 
 			if (tm2time(&tm, fsec, &result) != 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-						 errmsg("time out of range")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+									  errmsg("time out of range"))));
 
 			AdjustTimeForTypmod(&result, *typmod);
 
@@ -4173,11 +4310,12 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 	}
 	else
 	{
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("datetime format is not dated and not timed")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("datetime format is not dated and not timed"))));
 	}
 
+on_error:
 	return (Datum) 0;
 }
 
@@ -4193,17 +4331,24 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
  *
  * The TmFromChar is then analysed and converted into the final results in
  * struct 'tm' and 'fsec'.
+ *
+ * Bit mask of date/time/zone components found in 'fmt' is returned in 'flags'.
+ *
+ * 'std' specifies standard parsing mode.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
 do_to_timestamp(text *date_txt, text *fmt, bool std,
 				struct pg_tm *tm, fsec_t *fsec, int *fprec,
-				uint32 *flags)
+				uint32 *flags, bool *have_error)
 {
-	FormatNode *format;
+	FormatNode *format = NULL;
 	TmFromChar	tmfc;
 	int			fmt_len;
 	char	   *date_str;
 	int			fmask;
+	bool		incache = false;
 
 	date_str = text_to_cstring(date_txt);
 
@@ -4217,7 +4362,6 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 	if (fmt_len)
 	{
 		char	   *fmt_str;
-		bool		incache;
 
 		fmt_str = text_to_cstring(fmt);
 
@@ -4227,8 +4371,6 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 			 * Allocate new memory if format picture is bigger than static
 			 * cache and do not use cache (call parser always)
 			 */
-			incache = false;
-
 			format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode));
 
 			parse_format(format, fmt_str, DCH_keywords, DCH_suff, DCH_index,
@@ -4250,15 +4392,21 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 		/* dump_index(DCH_keywords, DCH_index); */
 #endif
 
-		DCH_from_char(format, date_str, &tmfc, std);
+		DCH_from_char(format, date_str, &tmfc, std, have_error);
+		CHECK_ERROR;
 
 		pfree(fmt_str);
 
 		if (flags)
-			*flags = DCH_datetime_type(format);
+			*flags = DCH_datetime_type(format, have_error);
 
 		if (!incache)
+		{
 			pfree(format);
+			format = NULL;
+		}
+
+		CHECK_ERROR;
 	}
 
 	DEBUG_TMFC(&tmfc);
@@ -4287,11 +4435,13 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 	if (tmfc.clock == CLOCK_12_HOUR)
 	{
 		if (tm->tm_hour < 1 || tm->tm_hour > HOURS_PER_DAY / 2)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("hour \"%d\" is invalid for the 12-hour clock",
-							tm->tm_hour),
-					 errhint("Use the 24-hour clock, or give an hour between 1 and 12.")));
+		{
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("hour \"%d\" is invalid for the 12-hour clock",
+										 tm->tm_hour),
+								  errhint("Use the 24-hour clock, or give an hour between 1 and 12."))));
+		}
 
 		if (tmfc.pm && tm->tm_hour < HOURS_PER_DAY / 2)
 			tm->tm_hour += HOURS_PER_DAY / 2;
@@ -4395,9 +4545,11 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 		 */
 
 		if (!tm->tm_year && !tmfc.bc)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("cannot calculate day of year without year information")));
+		{
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("cannot calculate day of year without year information"))));
+		}
 
 		if (tmfc.mode == FROM_CHAR_DATE_ISOWEEK)
 		{
@@ -4454,7 +4606,7 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 			 * said DTERR_MD_FIELD_OVERFLOW, because we don't want to print an
 			 * irrelevant hint about datestyle.
 			 */
-			DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp");
+			RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp"));
 		}
 	}
 
@@ -4463,7 +4615,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 		tm->tm_min < 0 || tm->tm_min >= MINS_PER_HOUR ||
 		tm->tm_sec < 0 || tm->tm_sec >= SECS_PER_MINUTE ||
 		*fsec < INT64CONST(0) || *fsec >= USECS_PER_SEC)
-		DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp");
+	{
+		RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp"));
+	}
 
 	/* Save parsed time-zone into tm->tm_zone if it was specified */
 	if (tmfc.tzsign)
@@ -4472,7 +4626,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 
 		if (tmfc.tzh < 0 || tmfc.tzh > MAX_TZDISP_HOUR ||
 			tmfc.tzm < 0 || tmfc.tzm >= MINS_PER_HOUR)
-			DateTimeParseError(DTERR_TZDISP_OVERFLOW, date_str, "timestamp");
+		{
+			RETURN_ERROR(DateTimeParseError(DTERR_TZDISP_OVERFLOW, date_str, "timestamp"));
+		}
 
 		tz = psprintf("%c%02d:%02d",
 					  tmfc.tzsign > 0 ? '+' : '-', tmfc.tzh, tmfc.tzm);
@@ -4482,6 +4638,11 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 
 	DEBUG_TM(tm);
 
+on_error:
+
+	if (format && !incache)
+		pfree(format);
+
 	pfree(date_str);
 }
 
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 2931bd58c0d..84bc97d40c3 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -329,11 +329,11 @@ timestamp_scale(PG_FUNCTION_ARGS)
 }
 
 /*
- * AdjustTimestampForTypmod --- round off a timestamp to suit given typmod
+ * AdjustTimestampForTypmodError --- round off a timestamp to suit given typmod
  * Works for either timestamp or timestamptz.
  */
-void
-AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
+bool
+AdjustTimestampForTypmodError(Timestamp *time, int32 typmod, bool *error)
 {
 	static const int64 TimestampScales[MAX_TIMESTAMP_PRECISION + 1] = {
 		INT64CONST(1000000),
@@ -359,10 +359,18 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
 		&& (typmod != -1) && (typmod != MAX_TIMESTAMP_PRECISION))
 	{
 		if (typmod < 0 || typmod > MAX_TIMESTAMP_PRECISION)
+		{
+			if (error)
+			{
+				*error = true;
+				return false;
+			}
+
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("timestamp(%d) precision must be between %d and %d",
 							typmod, 0, MAX_TIMESTAMP_PRECISION)));
+		}
 
 		if (*time >= INT64CONST(0))
 		{
@@ -375,8 +383,15 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
 					  * TimestampScales[typmod]);
 		}
 	}
+
+	return true;
 }
 
+void
+AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
+{
+	(void) AdjustTimestampForTypmodError(time, typmod, NULL);
+}
 
 /* timestamptz_in()
  * Convert a string to internal form.
@@ -5172,8 +5187,15 @@ timestamp_timestamptz(PG_FUNCTION_ARGS)
 	PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
 }
 
-static TimestampTz
-timestamp2timestamptz(Timestamp timestamp)
+/*
+ * Convert timestamp to timestamp with time zone.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero is returned.
+ */
+
+TimestampTz
+timestamp2timestamptz_opt_error(Timestamp timestamp, bool *have_error)
 {
 	TimestampTz result;
 	struct pg_tm tt,
@@ -5182,23 +5204,33 @@ timestamp2timestamptz(Timestamp timestamp)
 	int			tz;
 
 	if (TIMESTAMP_NOT_FINITE(timestamp))
-		result = timestamp;
-	else
-	{
-		if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("timestamp out of range")));
+		return timestamp;
 
+	if (!timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL))
+	{
 		tz = DetermineTimeZoneOffset(tm, session_timezone);
 
-		if (tm2timestamp(tm, fsec, &tz, &result) != 0)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("timestamp out of range")));
+		if (!tm2timestamp(tm, fsec, &tz, &result))
+			return result;
 	}
 
-	return result;
+	if (have_error)
+		*have_error = true;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+				 errmsg("timestamp out of range")));
+
+	return 0;
+}
+
+/*
+ * Single-argument version of timestamp2timestamptz_opt_error().
+ */
+static TimestampTz
+timestamp2timestamptz(Timestamp timestamp)
+{
+	return timestamp2timestamptz_opt_error(timestamp, NULL);
 }
 
 /* timestamptz_timestamp()
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index bd15bfa5bb0..c29f13aaf04 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -70,6 +70,8 @@ typedef struct
 /* date.c */
 extern int32 anytime_typmod_check(bool istz, int32 typmod);
 extern double date2timestamp_no_overflow(DateADT dateVal);
+extern Timestamp date2timestamp_opt_error(DateADT dateVal, bool *have_error);
+extern TimestampTz date2timestamptz_opt_error(DateADT dateVal, bool *have_error);
 extern void EncodeSpecialDate(DateADT dt, char *str);
 extern DateADT GetSQLCurrentDate(void);
 extern TimeTzADT *GetSQLCurrentTime(int32 typmod);
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index 0cafdd26538..5ebf336071b 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -337,5 +337,7 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
 extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
 
 extern void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
+extern bool AdjustTimestampForTypmodError(Timestamp *time, int32 typmod,
+										  bool *error);
 
 #endif							/* DATETIME_H */
diff --git a/src/include/utils/formatting.h b/src/include/utils/formatting.h
index beeaf10c332..165ca78ba5c 100644
--- a/src/include/utils/formatting.h
+++ b/src/include/utils/formatting.h
@@ -27,6 +27,7 @@ extern char *asc_toupper(const char *buff, size_t nbytes);
 extern char *asc_initcap(const char *buff, size_t nbytes);
 
 extern Datum parse_datetime(text *date_txt, text *fmt, bool std,
-							Oid *typid, int32 *typmod, int *tz);
+							Oid *typid, int32 *typmod, int *tz,
+							bool *have_error);
 
 #endif
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index ea16190ec3d..e884d4405f5 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -97,6 +97,9 @@ extern int	timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
 /* timestamp comparison works for timestamptz also */
 #define timestamptz_cmp_internal(dt1,dt2)	timestamp_cmp_internal(dt1, dt2)
 
+extern TimestampTz timestamp2timestamptz_opt_error(Timestamp timestamp,
+												   bool *have_error);
+
 extern int	isoweek2j(int year, int week);
 extern void isoweek2date(int woy, int *year, int *mon, int *mday);
 extern void isoweekdate2date(int isoweek, int wday, int *year, int *mon, int *mday);
-- 
2.14.3

0005-Allow-datetime-values-in-JsonbValue-7.patchapplication/octet-stream; name=0005-Allow-datetime-values-in-JsonbValue-7.patchDownload
From 7d6b25760c6b60c3b9e5284dfdbd9bea3f10cc53 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Sat, 20 Jul 2019 21:50:48 +0300
Subject: [PATCH 5/6] Allow datetime values in JsonbValue

SQL/JSON standard allows manipulation with datetime values.  So, it appears to
be convinient to allow datetime values to be represented in JsonbValue struct.
These datetime values are allowed for temporary representation only.  During
serialization datetime values are converted into strings.

SQL/JSON requires writing timestamps with timezone in the same timezone offset
as they were parsed.  This is why we allow storage of timezone offset in
JsonbValue struct.  For the same reason timezone offset argument is added to
JsonEncodeDateTime() function.

Extracted from original patch by Nikita Glukhov, Teodor Sigaev, Oleg Bartunov.
Revised by me.  Comments were adjusted by Liudmila Mantrova.

Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
Discussion: https://postgr.es/m/CAPpHfdsZgYEra_PeCLGNoXOWYx6iU-S3wF8aX0ObQUcZU%2B4XTw%40mail.gmail.com
Author: Nikita Glukhov, Teodor Sigaev, Oleg Bartunov, Alexander Korotkov, Liudmila Mantrova
Reviewed-by: Anastasia Lubennikova, Peter Eisentraut
---
 src/backend/utils/adt/json.c       | 32 ++++++++++++++++++++++++++------
 src/backend/utils/adt/jsonb.c      | 27 ++++++++++++++++++++++++---
 src/backend/utils/adt/jsonb_util.c | 21 +++++++++++++++++++++
 src/include/utils/jsonapi.h        |  3 ++-
 src/include/utils/jsonb.h          | 24 +++++++++++++++++++++---
 5 files changed, 94 insertions(+), 13 deletions(-)

diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index 26d293709aa..d4ba3bd87db 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -1506,7 +1506,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 			{
 				char		buf[MAXDATELEN + 1];
 
-				JsonEncodeDateTime(buf, val, DATEOID);
+				JsonEncodeDateTime(buf, val, DATEOID, NULL);
 				appendStringInfo(result, "\"%s\"", buf);
 			}
 			break;
@@ -1514,7 +1514,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 			{
 				char		buf[MAXDATELEN + 1];
 
-				JsonEncodeDateTime(buf, val, TIMESTAMPOID);
+				JsonEncodeDateTime(buf, val, TIMESTAMPOID, NULL);
 				appendStringInfo(result, "\"%s\"", buf);
 			}
 			break;
@@ -1522,7 +1522,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 			{
 				char		buf[MAXDATELEN + 1];
 
-				JsonEncodeDateTime(buf, val, TIMESTAMPTZOID);
+				JsonEncodeDateTime(buf, val, TIMESTAMPTZOID, NULL);
 				appendStringInfo(result, "\"%s\"", buf);
 			}
 			break;
@@ -1550,10 +1550,11 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 
 /*
  * Encode 'value' of datetime type 'typid' into JSON string in ISO format using
- * optionally preallocated buffer 'buf'.
+ * optionally preallocated buffer 'buf'.  Optional 'tzp' determines time-zone
+ * offset (in seconds) in which we want to show timestamptz.
  */
 char *
-JsonEncodeDateTime(char *buf, Datum value, Oid typid)
+JsonEncodeDateTime(char *buf, Datum value, Oid typid, const int *tzp)
 {
 	if (!buf)
 		buf = palloc(MAXDATELEN + 1);
@@ -1630,11 +1631,30 @@ JsonEncodeDateTime(char *buf, Datum value, Oid typid)
 				const char *tzn = NULL;
 
 				timestamp = DatumGetTimestampTz(value);
+
+				/*
+				 * If a time zone is specified, we apply the time-zone shift,
+				 * convert timestamptz to pg_tm as if it were without a time
+				 * zone, and then use the specified time zone for converting
+				 * the timestamp into a string.
+				 */
+				if (tzp)
+				{
+					tz = *tzp;
+					timestamp -= (TimestampTz) tz * USECS_PER_SEC;
+				}
+
 				/* Same as timestamptz_out(), but forcing DateStyle */
 				if (TIMESTAMP_NOT_FINITE(timestamp))
 					EncodeSpecialTimestamp(timestamp, buf);
-				else if (timestamp2tm(timestamp, &tz, &tm, &fsec, &tzn, NULL) == 0)
+				else if (timestamp2tm(timestamp, tzp ? NULL : &tz, &tm, &fsec,
+									  tzp ? NULL : &tzn, NULL) == 0)
+				{
+					if (tzp)
+						tm.tm_isdst = 1;	/* set time-zone presence flag */
+
 					EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf);
+				}
 				else
 					ereport(ERROR,
 							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 69f41ab4556..74b4bbe44c6 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -206,6 +206,24 @@ JsonbTypeName(JsonbValue *jbv)
 			return "boolean";
 		case jbvNull:
 			return "null";
+		case jbvDatetime:
+			switch (jbv->val.datetime.typid)
+			{
+				case DATEOID:
+					return "date";
+				case TIMEOID:
+					return "time without time zone";
+				case TIMETZOID:
+					return "time with time zone";
+				case TIMESTAMPOID:
+					return "timestamp without time zone";
+				case TIMESTAMPTZOID:
+					return "timestamp with time zone";
+				default:
+					elog(ERROR, "unrecognized jsonb value datetime type: %d",
+						 jbv->val.datetime.typid);
+			}
+			return "unknown";
 		default:
 			elog(ERROR, "unrecognized jsonb value type: %d", jbv->type);
 			return "unknown";
@@ -805,17 +823,20 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result,
 				break;
 			case JSONBTYPE_DATE:
 				jb.type = jbvString;
-				jb.val.string.val = JsonEncodeDateTime(NULL, val, DATEOID);
+				jb.val.string.val = JsonEncodeDateTime(NULL, val,
+													   DATEOID, NULL);
 				jb.val.string.len = strlen(jb.val.string.val);
 				break;
 			case JSONBTYPE_TIMESTAMP:
 				jb.type = jbvString;
-				jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPOID);
+				jb.val.string.val = JsonEncodeDateTime(NULL, val,
+													   TIMESTAMPOID, NULL);
 				jb.val.string.len = strlen(jb.val.string.val);
 				break;
 			case JSONBTYPE_TIMESTAMPTZ:
 				jb.type = jbvString;
-				jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPTZOID);
+				jb.val.string.val = JsonEncodeDateTime(NULL, val,
+													   TIMESTAMPTZOID, NULL);
 				jb.val.string.len = strlen(jb.val.string.val);
 				break;
 			case JSONBTYPE_JSONCAST:
diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index ac04c4a57bc..dee6e1bef8c 100644
--- a/src/backend/utils/adt/jsonb_util.c
+++ b/src/backend/utils/adt/jsonb_util.c
@@ -14,9 +14,12 @@
 #include "postgres.h"
 
 #include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
 #include "miscadmin.h"
 #include "utils/builtins.h"
+#include "utils/datetime.h"
 #include "utils/hashutils.h"
+#include "utils/jsonapi.h"
 #include "utils/jsonb.h"
 #include "utils/memutils.h"
 #include "utils/varlena.h"
@@ -242,6 +245,8 @@ compareJsonbContainers(JsonbContainer *a, JsonbContainer *b)
 						break;
 					case jbvBinary:
 						elog(ERROR, "unexpected jbvBinary value");
+					case jbvDatetime:
+						elog(ERROR, "unexpected jbvDatetime value");
 				}
 			}
 			else
@@ -1749,6 +1754,22 @@ convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal)
 				JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE;
 			break;
 
+		case jbvDatetime:
+			{
+				char		buf[MAXDATELEN + 1];
+				size_t		len;
+
+				JsonEncodeDateTime(buf,
+								   scalarVal->val.datetime.value,
+								   scalarVal->val.datetime.typid,
+								   &scalarVal->val.datetime.tz);
+				len = strlen(buf);
+				appendToBuffer(buffer, buf, len);
+
+				*jentry = len;
+			}
+			break;
+
 		default:
 			elog(ERROR, "invalid jsonb scalar type");
 	}
diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h
index 1c56acca55a..e1dab24d5dd 100644
--- a/src/include/utils/jsonapi.h
+++ b/src/include/utils/jsonapi.h
@@ -161,6 +161,7 @@ extern Jsonb *transform_jsonb_string_values(Jsonb *jsonb, void *action_state,
 extern text *transform_json_string_values(text *json, void *action_state,
 										  JsonTransformStringValuesAction transform_action);
 
-extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid);
+extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid,
+								const int *tzp);
 
 #endif							/* JSONAPI_H */
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index ac52b75f51d..80ef327cc09 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -241,7 +241,15 @@ enum jbvType
 	jbvArray = 0x10,
 	jbvObject,
 	/* Binary (i.e. struct Jsonb) jbvArray/jbvObject */
-	jbvBinary
+	jbvBinary,
+
+	/*
+	 * Virtual types.
+	 *
+	 * These types are used only for in-memory JSON processing and serialized
+	 * into JSON strings when outputted to json/jsonb.
+	 */
+	jbvDatetime = 0x20,
 };
 
 /*
@@ -282,11 +290,21 @@ struct JsonbValue
 			int			len;
 			JsonbContainer *data;
 		}			binary;		/* Array or object, in on-disk format */
+
+		struct
+		{
+			Datum		value;
+			Oid			typid;
+			int32		typmod;
+			int			tz;		/* Numeric time zone, in seconds, for
+								 * TimestampTz data type */
+		}			datetime;
 	}			val;
 };
 
-#define IsAJsonbScalar(jsonbval)	((jsonbval)->type >= jbvNull && \
-									 (jsonbval)->type <= jbvBool)
+#define IsAJsonbScalar(jsonbval)	(((jsonbval)->type >= jbvNull && \
+									  (jsonbval)->type <= jbvBool) || \
+									  (jsonbval)->type == jbvDatetime)
 
 /*
  * Key/value pair within an Object.
-- 
2.14.3

0006-Implement-jsonpath-.datetime-method-7.patchapplication/octet-stream; name=0006-Implement-jsonpath-.datetime-method-7.patchDownload
From 9bb0d6c36dc0e609076f7499f6f3832b3ffe71c6 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Sun, 21 Jul 2019 01:16:38 +0300
Subject: [PATCH 6/6] Implement jsonpath .datetime() method

This commit implements jsonpath .datetime() method as it's specified in
SQL/JSON standard.  There are no-argument and single-argument versions of
this method.  No-argument version selects first of ISO datetime formats
matching input string.  Single-argument version accepts template string as
its argument.

Additionally to .datetime() method itself this commit also implements
comparison ability of resulting date and time values.  There is some difficulty
because exising jsonb_path_*() functions are immutable, while comparison of
timezoned and non-timezoned types involves current timezone.  At first, current
timezone could be changes in session.  Moreover, timezones themselves are not
immutable and could be updated.  This is why we let existing immutable functions
throw errors on such non-immutable comparison.  In the same time this commit
provides jsonb_path_*_tz() functions which are stable and support operations
involving timezones.

Support of .datetime() method was the only blocker prevents T832 from being
marked as supported.  sql_features.txt is updated correspondingly.

Extracted from original patch by Nikita Glukhov, Teodor Sigaev, Oleg Bartunov.
Heavily revised by me.  Comments were adjusted by Liudmila Mantrova.

Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
Discussion: https://postgr.es/m/CAPpHfdsZgYEra_PeCLGNoXOWYx6iU-S3wF8aX0ObQUcZU%2B4XTw%40mail.gmail.com
Author: Alexander Korotkov, Nikita Glukhov, Teodor Sigaev, Oleg Bartunov, Liudmila Mantrova
Reviewed-by: Anastasia Lubennikova, Peter Eisentraut
---
 doc/src/sgml/func.sgml                       | 106 +++++-
 src/backend/catalog/sql_features.txt         |   2 +-
 src/backend/catalog/system_views.sql         |  40 ++
 src/backend/utils/adt/jsonpath.c             |  24 +-
 src/backend/utils/adt/jsonpath_exec.c        | 472 ++++++++++++++++++++++--
 src/backend/utils/adt/jsonpath_gram.y        |  14 +
 src/backend/utils/adt/jsonpath_scan.l        |   1 +
 src/backend/utils/errcodes.txt               |   1 +
 src/include/catalog/pg_proc.dat              |  22 ++
 src/include/utils/jsonpath.h                 |   1 +
 src/test/regress/expected/jsonb_jsonpath.out | 526 +++++++++++++++++++++++++++
 src/test/regress/expected/jsonpath.out       |  12 +
 src/test/regress/sql/jsonb_jsonpath.sql      | 172 +++++++++
 src/test/regress/sql/jsonpath.sql            |   2 +
 14 files changed, 1351 insertions(+), 44 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index e5c6fe89c2f..b2d7f1d5afe 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -11793,16 +11793,6 @@ table2-mapping
   </para>
 
   <itemizedlist>
-   <listitem>
-    <para>
-     <literal>.datetime()</literal> item method is not implemented yet
-     mainly because immutable <type>jsonpath</type> functions and operators
-     cannot reference session timezone, which is used in some datetime
-     operations.  Datetime support will be added to <type>jsonpath</type>
-     in future versions of <productname>PostgreSQL</productname>.
-    </para>
-   </listitem>
-
    <listitem>
     <para>
      A path expression can be a Boolean predicate, although the SQL/JSON
@@ -12008,6 +11998,20 @@ table2-mapping
         <entry><literal>$.z.abs()</literal></entry>
         <entry><literal>0.3</literal></entry>
        </row>
+       <row>
+        <entry><literal>datetime()</literal></entry>
+        <entry>Date/time value converted from a string</entry>
+        <entry><literal>["2015-8-1", "2015-08-12"]</literal></entry>
+        <entry><literal>$[*] ? (@.datetime() &lt; "2015-08-2". datetime())</literal></entry>
+        <entry><literal>2015-8-1</literal></entry>
+       </row>
+       <row>
+        <entry><literal>datetime(<replaceable>template</replaceable>)</literal></entry>
+        <entry>Date/time value converted from a string using the specified template</entry>
+        <entry><literal>["12:30", "18:40"]</literal></entry>
+        <entry><literal>$[*].datetime("HH24:MI")</literal></entry>
+        <entry><literal>"12:30:00", "18:40:00"</literal></entry>
+       </row>
        <row>
         <entry><literal>keyvalue()</literal></entry>
         <entry>
@@ -12025,6 +12029,26 @@ table2-mapping
      </tgroup>
     </table>
 
+    <note>
+     <para>
+      The result type of <literal>datetime()</literal> and
+      <literal>datetime(<replaceable>template</replaceable>)</literal>
+      methods can be <type>date</type>, <type>timetz</type>, <type>time</type>,
+      <type>timestamptz</type>, or <type>timestamp</type>.
+      Both methods determine the result type dynamically.
+     </para>
+     <para>
+      The <literal>datetime()</literal> method sequentially tries ISO formats
+      for <type>date</type>, <type>timetz</type>, <type>time</type>,
+      <type>timestamptz</type>, and <type>timestamp</type>. It stops on
+      the first matching format and the corresponding data type.
+     </para>
+     <para>
+      The <literal>datetime(<replaceable>template</replaceable>)</literal>
+      method determines the result type by the provided template string.
+     </para>
+    </note>
+
     <table id="functions-sqljson-filter-ex-table">
      <title><type>jsonpath</type> Filter Expression Elements</title>
      <tgroup cols="5">
@@ -12167,6 +12191,15 @@ table2-mapping
       </tbody>
      </tgroup>
     </table>
+
+    <note>
+     <para>
+      When different date/time values are compared, an implicit cast is
+      applied. A <type>date</type> value can be cast to <type>timestamp</type>
+      or <type>timestamptz</type>, <type>timestamp</type> can be cast to
+      <type>timestamptz</type>, and <type>time</type> &mdash; to <type>timetz</type>.
+     </para>
+    </note>
    </sect3>
 
   </sect2>
@@ -12399,7 +12432,7 @@ table2-mapping
    <para>
     The <literal>@?</literal> and <literal>@@</literal> operators suppress
     the following errors: lacking object field or array element, unexpected
-    JSON item type, and numeric errors.
+    JSON item type, datetime and numeric errors.
     This behavior might be helpful while searching over JSON document
     collections of varying structure.
    </para>
@@ -12668,18 +12701,33 @@ table2-mapping
   <indexterm>
    <primary>jsonb_path_exists</primary>
   </indexterm>
+  <indexterm>
+   <primary>jsonb_path_exists_tz</primary>
+  </indexterm>
   <indexterm>
    <primary>jsonb_path_match</primary>
   </indexterm>
+  <indexterm>
+   <primary>jsonb_path_match_tz</primary>
+  </indexterm>
   <indexterm>
    <primary>jsonb_path_query</primary>
   </indexterm>
+  <indexterm>
+   <primary>jsonb_path_query_tz</primary>
+  </indexterm>
   <indexterm>
    <primary>jsonb_path_query_array</primary>
   </indexterm>
+  <indexterm>
+   <primary>jsonb_path_query_array_tz</primary>
+  </indexterm>
   <indexterm>
    <primary>jsonb_path_query_first</primary>
   </indexterm>
+  <indexterm>
+   <primary>jsonb_path_query_first_tz</primary>
+  </indexterm>
 
   <table id="functions-json-processing-table">
     <title>JSON Processing Functions</title>
@@ -13019,6 +13067,9 @@ table2-mapping
          <para><literal>
            jsonb_path_exists(target jsonb, path jsonpath [, vars jsonb [, silent bool]])
          </literal></para>
+         <para><literal>
+           jsonb_path_exists_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>boolean</type></entry>
         <entry>
@@ -13039,6 +13090,9 @@ table2-mapping
          <para><literal>
            jsonb_path_match(target jsonb, path jsonpath [, vars jsonb, silent bool])
          </literal></para>
+         <para><literal>
+           jsonb_path_match_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>boolean</type></entry>
         <entry>
@@ -13060,6 +13114,9 @@ table2-mapping
          <para><literal>
            jsonb_path_query(target jsonb, path jsonpath [, vars jsonb, silent bool])
          </literal></para>
+         <para><literal>
+           jsonb_path_query_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>setof jsonb</type></entry>
         <entry>
@@ -13088,6 +13145,9 @@ table2-mapping
          <para><literal>
            jsonb_path_query_array(target jsonb, path jsonpath [, vars jsonb, silent bool])
          </literal></para>
+         <para><literal>
+           jsonb_path_query_array_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>jsonb</type></entry>
         <entry>
@@ -13108,6 +13168,9 @@ table2-mapping
          <para><literal>
            jsonb_path_query_first(target jsonb, path jsonpath [, vars jsonb, silent bool])
          </literal></para>
+         <para><literal>
+           jsonb_path_query_first_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>jsonb</type></entry>
         <entry>
@@ -13250,11 +13313,8 @@ table2-mapping
 
   <note>
    <para>
-    The <literal>jsonb_path_exists</literal>, <literal>jsonb_path_match</literal>,
-    <literal>jsonb_path_query</literal>, <literal>jsonb_path_query_array</literal>, and
-    <literal>jsonb_path_query_first</literal>
-    functions have optional <literal>vars</literal> and <literal>silent</literal>
-    arguments.
+    The <literal>jsonb_path_*</literal> functions have optional
+    <literal>vars</literal> and <literal>silent</literal> arguments.
    </para>
    <para>
     If the <parameter>vars</parameter> argument is specified, it provides an
@@ -13268,6 +13328,20 @@ table2-mapping
    </para>
   </note>
 
+  <note>
+   <para>
+    Some of the <literal>jsonb_path_*</literal> functions have the
+    <literal>_tz</literal> suffix. These functions have been implemented to
+    support comparison of date/time values that involves implicit
+    timezone-aware casts. Since operations with time zones are not immutable,
+    these functions are qualified as stable. Their counterparts without the
+    suffix do not support such casts, so they are immutable and can be used for
+    such use-cases as expression indexes
+    (see <xref linkend="indexes-expressional"/>). There is no difference
+    between these functions for other <type>jsonpath</type> operations.
+   </para>
+  </note>
+
   <para>
     See also <xref linkend="functions-aggregate"/> for the aggregate
     function <function>json_agg</function> which aggregates record
diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index 059ec02cd03..9c68292a54d 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -544,7 +544,7 @@ T828	JSON_QUERY			NO
 T829	JSON_QUERY: array wrapper options			NO	
 T830	Enforcing unique keys in SQL/JSON constructor functions			NO	
 T831	SQL/JSON path language: strict mode			YES	
-T832	SQL/JSON path language: item method			NO	datetime() not yet implemented
+T832	SQL/JSON path language: item method			YES	
 T833	SQL/JSON path language: multiple subscripts			YES	
 T834	SQL/JSON path language: wildcard member accessor			YES	
 T835	SQL/JSON path language: filter expressions			YES	
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ea4c85e3959..423ae4fdc8a 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1287,6 +1287,46 @@ LANGUAGE INTERNAL
 STRICT IMMUTABLE PARALLEL SAFE
 AS 'jsonb_path_query_first';
 
+CREATE OR REPLACE FUNCTION
+  jsonb_path_exists_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                    silent boolean DEFAULT false)
+RETURNS boolean
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_exists_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_match_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                   silent boolean DEFAULT false)
+RETURNS boolean
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_match_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_query_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                   silent boolean DEFAULT false)
+RETURNS SETOF jsonb
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_query_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_query_array_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                         silent boolean DEFAULT false)
+RETURNS jsonb
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_query_array_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_query_first_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                         silent boolean DEFAULT false)
+RETURNS jsonb
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_query_first_tz';
+
 --
 -- The default permissions for functions mean that anyone can execute them.
 -- A number of functions shouldn't be executable by just anyone, but rather
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 87ae60e490f..65c047acc86 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -337,12 +337,14 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
 		case jpiPlus:
 		case jpiMinus:
 		case jpiExists:
+		case jpiDatetime:
 			{
 				int32		arg = reserveSpaceForItemPointer(buf);
 
-				chld = flattenJsonPathParseItem(buf, item->value.arg,
-												nestingLevel + argNestingLevel,
-												insideArraySubscript);
+				chld = !item->value.arg ? pos :
+					flattenJsonPathParseItem(buf, item->value.arg,
+											 nestingLevel + argNestingLevel,
+											 insideArraySubscript);
 				*(int32 *) (buf->data + arg) = chld - pos;
 			}
 			break;
@@ -692,6 +694,15 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
 		case jpiDouble:
 			appendBinaryStringInfo(buf, ".double()", 9);
 			break;
+		case jpiDatetime:
+			appendBinaryStringInfo(buf, ".datetime(", 10);
+			if (v->content.arg)
+			{
+				jspGetArg(v, &elem);
+				printJsonPathItem(buf, &elem, false, false);
+			}
+			appendStringInfoChar(buf, ')');
+			break;
 		case jpiKeyValue:
 			appendBinaryStringInfo(buf, ".keyvalue()", 11);
 			break;
@@ -754,6 +765,8 @@ jspOperationName(JsonPathItemType type)
 			return "floor";
 		case jpiCeiling:
 			return "ceiling";
+		case jpiDatetime:
+			return "datetime";
 		default:
 			elog(ERROR, "unrecognized jsonpath item type: %d", type);
 			return NULL;
@@ -889,6 +902,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
 		case jpiPlus:
 		case jpiMinus:
 		case jpiFilter:
+		case jpiDatetime:
 			read_int32(v->content.arg, base, pos);
 			break;
 		case jpiIndexArray:
@@ -913,7 +927,8 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a)
 		   v->type == jpiIsUnknown ||
 		   v->type == jpiExists ||
 		   v->type == jpiPlus ||
-		   v->type == jpiMinus);
+		   v->type == jpiMinus ||
+		   v->type == jpiDatetime);
 
 	jspInitByBuffer(a, v->base, v->content.arg);
 }
@@ -961,6 +976,7 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
 			   v->type == jpiFloor ||
 			   v->type == jpiCeiling ||
 			   v->type == jpiDouble ||
+			   v->type == jpiDatetime ||
 			   v->type == jpiKeyValue ||
 			   v->type == jpiStartsWith);
 
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index 21106e1da86..9dcc91da5c0 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -66,6 +66,7 @@
 #include "miscadmin.h"
 #include "regex/regex.h"
 #include "utils/builtins.h"
+#include "utils/datetime.h"
 #include "utils/datum.h"
 #include "utils/formatting.h"
 #include "utils/float.h"
@@ -107,6 +108,7 @@ typedef struct JsonPathExecContext
 										 * ignored */
 	bool		throwErrors;	/* with "false" all suppressible errors are
 								 * suppressed */
+	bool		useTz;
 } JsonPathExecContext;
 
 /* Context for LIKE_REGEX execution. */
@@ -173,7 +175,8 @@ typedef JsonPathBool (*JsonPathPredicateCallback) (JsonPathItem *jsp,
 typedef Numeric (*BinaryArithmFunc) (Numeric num1, Numeric num2, bool *error);
 
 static JsonPathExecResult executeJsonPath(JsonPath *path, Jsonb *vars,
-										  Jsonb *json, bool throwErrors, JsonValueList *result);
+										  Jsonb *json, bool throwErrors,
+										  JsonValueList *result, bool useTz);
 static JsonPathExecResult executeItem(JsonPathExecContext *cxt,
 									  JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
 static JsonPathExecResult executeItemOptUnwrapTarget(JsonPathExecContext *cxt,
@@ -214,6 +217,8 @@ static JsonPathBool executeLikeRegex(JsonPathItem *jsp, JsonbValue *str,
 static JsonPathExecResult executeNumericItemMethod(JsonPathExecContext *cxt,
 												   JsonPathItem *jsp, JsonbValue *jb, bool unwrap, PGFunction func,
 												   JsonValueList *found);
+static JsonPathExecResult executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
+												JsonbValue *jb, JsonValueList *found);
 static JsonPathExecResult executeKeyValueMethod(JsonPathExecContext *cxt,
 												JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
 static JsonPathExecResult appendBoolResult(JsonPathExecContext *cxt,
@@ -225,7 +230,8 @@ static void getJsonPathVariable(JsonPathExecContext *cxt,
 static int	JsonbArraySize(JsonbValue *jb);
 static JsonPathBool executeComparison(JsonPathItem *cmp, JsonbValue *lv,
 									  JsonbValue *rv, void *p);
-static JsonPathBool compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2);
+static JsonPathBool compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2,
+								 bool useTz);
 static int	compareNumeric(Numeric a, Numeric b);
 static JsonbValue *copyJsonbValue(JsonbValue *src);
 static JsonPathExecResult getArrayIndex(JsonPathExecContext *cxt,
@@ -246,6 +252,8 @@ static JsonbValue *JsonbInitBinary(JsonbValue *jbv, Jsonb *jb);
 static int	JsonbType(JsonbValue *jb);
 static JsonbValue *getScalar(JsonbValue *scalar, enum jbvType type);
 static JsonbValue *wrapItemsInArray(const JsonValueList *items);
+static int	compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
+							bool useTz, bool *have_error);
 
 /****************** User interface to JsonPath executor ********************/
 
@@ -261,8 +269,8 @@ static JsonbValue *wrapItemsInArray(const JsonValueList *items);
  *		SQL/JSON.  Regarding jsonb_path_match(), this function doesn't have
  *		an analogy in SQL/JSON, so we define its behavior on our own.
  */
-Datum
-jsonb_path_exists(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_exists_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -276,7 +284,7 @@ jsonb_path_exists(PG_FUNCTION_ARGS)
 		silent = PG_GETARG_BOOL(3);
 	}
 
-	res = executeJsonPath(jp, vars, jb, !silent, NULL);
+	res = executeJsonPath(jp, vars, jb, !silent, NULL, tz);
 
 	PG_FREE_IF_COPY(jb, 0);
 	PG_FREE_IF_COPY(jp, 1);
@@ -287,6 +295,18 @@ jsonb_path_exists(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(res == jperOk);
 }
 
+Datum
+jsonb_path_exists(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_exists_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_exists_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_exists_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_exists_opr
  *		Implementation of operator "jsonb @? jsonpath" (2-argument version of
@@ -296,7 +316,7 @@ Datum
 jsonb_path_exists_opr(PG_FUNCTION_ARGS)
 {
 	/* just call the other one -- it can handle both cases */
-	return jsonb_path_exists(fcinfo);
+	return jsonb_path_exists_internal(fcinfo, false);
 }
 
 /*
@@ -304,8 +324,8 @@ jsonb_path_exists_opr(PG_FUNCTION_ARGS)
  *		Returns jsonpath predicate result item for the specified jsonb value.
  *		See jsonb_path_exists() comment for details regarding error handling.
  */
-Datum
-jsonb_path_match(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_match_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -319,7 +339,7 @@ jsonb_path_match(PG_FUNCTION_ARGS)
 		silent = PG_GETARG_BOOL(3);
 	}
 
-	(void) executeJsonPath(jp, vars, jb, !silent, &found);
+	(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 	PG_FREE_IF_COPY(jb, 0);
 	PG_FREE_IF_COPY(jp, 1);
@@ -343,6 +363,18 @@ jsonb_path_match(PG_FUNCTION_ARGS)
 	PG_RETURN_NULL();
 }
 
+Datum
+jsonb_path_match(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_match_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_match_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_match_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_match_opr
  *		Implementation of operator "jsonb @@ jsonpath" (2-argument version of
@@ -352,7 +384,7 @@ Datum
 jsonb_path_match_opr(PG_FUNCTION_ARGS)
 {
 	/* just call the other one -- it can handle both cases */
-	return jsonb_path_match(fcinfo);
+	return jsonb_path_match_internal(fcinfo, false);
 }
 
 /*
@@ -360,8 +392,8 @@ jsonb_path_match_opr(PG_FUNCTION_ARGS)
  *		Executes jsonpath for given jsonb document and returns result as
  *		rowset.
  */
-Datum
-jsonb_path_query(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_query_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	FuncCallContext *funcctx;
 	List	   *found;
@@ -385,7 +417,7 @@ jsonb_path_query(PG_FUNCTION_ARGS)
 		vars = PG_GETARG_JSONB_P_COPY(2);
 		silent = PG_GETARG_BOOL(3);
 
-		(void) executeJsonPath(jp, vars, jb, !silent, &found);
+		(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 		funcctx->user_fctx = JsonValueListGetList(&found);
 
@@ -406,13 +438,25 @@ jsonb_path_query(PG_FUNCTION_ARGS)
 	SRF_RETURN_NEXT(funcctx, JsonbPGetDatum(JsonbValueToJsonb(v)));
 }
 
+Datum
+jsonb_path_query(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_query_array
  *		Executes jsonpath for given jsonb document and returns result as
  *		jsonb array.
  */
-Datum
-jsonb_path_query_array(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_query_array_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -420,18 +464,30 @@ jsonb_path_query_array(PG_FUNCTION_ARGS)
 	Jsonb	   *vars = PG_GETARG_JSONB_P(2);
 	bool		silent = PG_GETARG_BOOL(3);
 
-	(void) executeJsonPath(jp, vars, jb, !silent, &found);
+	(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 	PG_RETURN_JSONB_P(JsonbValueToJsonb(wrapItemsInArray(&found)));
 }
 
+Datum
+jsonb_path_query_array(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_array_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_array_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_array_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_query_first
  *		Executes jsonpath for given jsonb document and returns first result
  *		item.  If there are no items, NULL returned.
  */
-Datum
-jsonb_path_query_first(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_query_first_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -439,7 +495,7 @@ jsonb_path_query_first(PG_FUNCTION_ARGS)
 	Jsonb	   *vars = PG_GETARG_JSONB_P(2);
 	bool		silent = PG_GETARG_BOOL(3);
 
-	(void) executeJsonPath(jp, vars, jb, !silent, &found);
+	(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 	if (JsonValueListLength(&found) >= 1)
 		PG_RETURN_JSONB_P(JsonbValueToJsonb(JsonValueListHead(&found)));
@@ -447,6 +503,18 @@ jsonb_path_query_first(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 }
 
+Datum
+jsonb_path_query_first(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_first_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_first_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_first_internal(fcinfo, true);
+}
+
 /********************Execute functions for JsonPath**************************/
 
 /*
@@ -470,7 +538,7 @@ jsonb_path_query_first(PG_FUNCTION_ARGS)
  */
 static JsonPathExecResult
 executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors,
-				JsonValueList *result)
+				JsonValueList *result, bool useTz)
 {
 	JsonPathExecContext cxt;
 	JsonPathExecResult res;
@@ -500,6 +568,7 @@ executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors,
 	cxt.lastGeneratedObjectId = vars ? 2 : 1;
 	cxt.innermostArraySize = -1;
 	cxt.throwErrors = throwErrors;
+	cxt.useTz = useTz;
 
 	if (jspStrictAbsenseOfErrors(&cxt) && !result)
 	{
@@ -1028,6 +1097,12 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
 			}
 			break;
 
+		case jpiDatetime:
+			if (unwrap && JsonbType(jb) == jbvArray)
+				return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
+
+			return executeDateTimeMethod(cxt, jsp, jb, found);
+
 		case jpiKeyValue:
 			if (unwrap && JsonbType(jb) == jbvArray)
 				return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
@@ -1214,7 +1289,7 @@ executeBoolItem(JsonPathExecContext *cxt, JsonPathItem *jsp,
 			jspGetLeftArg(jsp, &larg);
 			jspGetRightArg(jsp, &rarg);
 			return executePredicate(cxt, jsp, &larg, &rarg, jb, true,
-									executeComparison, NULL);
+									executeComparison, cxt);
 
 		case jpiStartsWith:		/* 'whole STARTS WITH initial' */
 			jspGetLeftArg(jsp, &larg);	/* 'whole' */
@@ -1717,6 +1792,138 @@ executeNumericItemMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
 	return executeNextItem(cxt, jsp, &next, jb, found, false);
 }
 
+/*
+ * Implementation of the .datetime() method.
+ *
+ * Converts a string into a date/time value. The actual type is determined at run time.
+ * If an argument is provided, this argument is used as a template string.
+ * Otherwise, the first fitting ISO format is selected.
+ */
+static JsonPathExecResult
+executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
+					  JsonbValue *jb, JsonValueList *found)
+{
+	JsonbValue	jbvbuf;
+	Datum		value;
+	text	   *datetime;
+	Oid			typid;
+	int32		typmod = -1;
+	int			tz = 0;
+	bool		hasNext;
+	JsonPathExecResult res = jperNotFound;
+	JsonPathItem elem;
+
+	if (!(jb = getScalar(jb, jbvString)))
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+							  errmsg("invalid argument for SQL/JSON datetime function"),
+							  errdetail("jsonpath item method .%s() can only be applied to a string",
+										jspOperationName(jsp->type)))));
+
+	datetime = cstring_to_text_with_len(jb->val.string.val,
+										jb->val.string.len);
+
+	if (jsp->content.arg)
+	{
+		text	   *template;
+		char	   *template_str;
+		int			template_len;
+		bool		have_error = false;
+
+		jspGetArg(jsp, &elem);
+
+		if (elem.type != jpiString)
+			elog(ERROR, "invalid jsonpath item type for .datetime() argument");
+
+		template_str = jspGetString(&elem, &template_len);
+
+		template = cstring_to_text_with_len(template_str,
+											template_len);
+
+		value = parse_datetime(datetime, template, true,
+							   &typid, &typmod, &tz,
+							   jspThrowErrors(cxt) ? NULL : &have_error);
+
+		if (have_error)
+			res = jperError;
+		else
+			res = jperOk;
+	}
+	else
+	{
+		/*
+		 * According to SQL/JSON standard enumerate ISO formats for: date,
+		 * timetz, time, timestamptz, timestamp.
+		 */
+		static const char *fmt_str[] =
+		{
+			"yyyy-mm-dd",
+			"HH24:MI:SS TZH:TZM",
+			"HH24:MI:SS TZH",
+			"HH24:MI:SS",
+			"yyyy-mm-dd HH24:MI:SS TZH:TZM",
+			"yyyy-mm-dd HH24:MI:SS TZH",
+			"yyyy-mm-dd HH24:MI:SS"
+		};
+
+		/* cache for format texts */
+		static text *fmt_txt[lengthof(fmt_str)] = {0};
+		int			i;
+
+		/* loop until datetime format fits */
+		for (i = 0; i < lengthof(fmt_str); i++)
+		{
+			bool		have_error = false;
+
+			if (!fmt_txt[i])
+			{
+				MemoryContext oldcxt =
+				MemoryContextSwitchTo(TopMemoryContext);
+
+				fmt_txt[i] = cstring_to_text(fmt_str[i]);
+				MemoryContextSwitchTo(oldcxt);
+			}
+
+			value = parse_datetime(datetime, fmt_txt[i], true,
+								   &typid, &typmod, &tz,
+								   &have_error);
+
+			if (!have_error)
+			{
+				res = jperOk;
+				break;
+			}
+		}
+
+		if (res == jperNotFound)
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+								  errmsg("invalid argument for SQL/JSON datetime function"),
+								  errdetail("unrecognized datetime format"),
+								  errhint("use datetime template argument for explicit format specification"))));
+	}
+
+	pfree(datetime);
+
+	if (jperIsError(res))
+		return res;
+
+	hasNext = jspGetNext(jsp, &elem);
+
+	if (!hasNext && !found)
+		return res;
+
+	jb = hasNext ? &jbvbuf : palloc(sizeof(*jb));
+
+	jb->type = jbvDatetime;
+	jb->val.datetime.value = value;
+	jb->val.datetime.typid = typid;
+	jb->val.datetime.typmod = typmod;
+	jb->val.datetime.tz = tz;
+
+	return executeNextItem(cxt, jsp, &elem, jb, found, hasNext);
+}
+
 /*
  * Implementation of .keyvalue() method.
  *
@@ -1977,7 +2184,9 @@ JsonbArraySize(JsonbValue *jb)
 static JsonPathBool
 executeComparison(JsonPathItem *cmp, JsonbValue *lv, JsonbValue *rv, void *p)
 {
-	return compareItems(cmp->type, lv, rv);
+	JsonPathExecContext *cxt = (JsonPathExecContext *) p;
+
+	return compareItems(cmp->type, lv, rv, cxt->useTz);
 }
 
 /*
@@ -2072,7 +2281,7 @@ compareStrings(const char *mbstr1, int mblen1,
  * Compare two SQL/JSON items using comparison operation 'op'.
  */
 static JsonPathBool
-compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2)
+compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2, bool useTz)
 {
 	int			cmp;
 	bool		res;
@@ -2113,6 +2322,21 @@ compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2)
 			cmp = compareStrings(jb1->val.string.val, jb1->val.string.len,
 								 jb2->val.string.val, jb2->val.string.len);
 			break;
+		case jbvDatetime:
+			{
+				bool		have_error = false;
+
+				cmp = compareDatetime(jb1->val.datetime.value,
+									  jb1->val.datetime.typid,
+									  jb2->val.datetime.value,
+									  jb2->val.datetime.typid,
+									  useTz,
+									  &have_error);
+
+				if (have_error)
+					return jpbUnknown;
+			}
+			break;
 
 		case jbvBinary:
 		case jbvArray:
@@ -2372,3 +2596,205 @@ wrapItemsInArray(const JsonValueList *items)
 
 	return pushJsonbValue(&ps, WJB_END_ARRAY, NULL);
 }
+
+/*
+ * Cross-type comparison of two datetime SQL/JSON items.  If items are
+ * uncomparable, 'error' flag is set.
+ */
+static int
+compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
+				bool useTz, bool *have_error)
+{
+	PGFunction cmpfunc = NULL;
+
+	switch (typid1)
+	{
+		case DATEOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					cmpfunc = date_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					val1 = date2timestamp_opt_error(val1, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"date", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val1 = date2timestamptz_opt_error(val1, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMEOID:
+			switch (typid2)
+			{
+				case TIMEOID:
+					cmpfunc = time_cmp;
+
+					break;
+
+				case TIMETZOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"time", "timetz"),
+								 errhint("use *_tz() function for timezone support")));
+					val1 = DirectFunctionCall1(time_timetz, val1);
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case DATEOID:
+				case TIMESTAMPOID:
+				case TIMESTAMPTZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMETZOID:
+			switch (typid2)
+			{
+				case TIMEOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"time", "timetz"),
+								 errhint("use *_tz() function for timezone support")));
+					val2 = DirectFunctionCall1(time_timetz, val2);
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case TIMETZOID:
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case DATEOID:
+				case TIMESTAMPOID:
+				case TIMESTAMPTZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMESTAMPOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					val2 = date2timestamp_opt_error(val2, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"timestamp", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val1 = timestamp2timestamptz_opt_error(val1, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMESTAMPTZOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"date", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val2 = date2timestamptz_opt_error(val2, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"timestamp", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val2 = timestamp2timestamptz_opt_error(val2, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		default:
+			elog(ERROR, "unrecognized SQL/JSON datetime type oid: %d",
+				 typid1);
+	}
+
+	if (*have_error)
+		return 0;
+
+	if (!cmpfunc)
+		elog(ERROR, "unrecognized SQL/JSON datetime type oid: %d",
+			 typid2);
+
+	*have_error = false;
+
+	return DatumGetInt32(DirectFunctionCall2(cmpfunc, val1, val2));
+}
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
index 91b4b2f5985..059faeeb2c7 100644
--- a/src/backend/utils/adt/jsonpath_gram.y
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -94,12 +94,14 @@ static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr,
 %token	<str>		LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
 %token	<str>		ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P
 %token	<str>		ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P KEYVALUE_P
+%token	<str>		DATETIME_P
 
 %type	<result>	result
 
 %type	<value>		scalar_value path_primary expr array_accessor
 					any_path accessor_op key predicate delimited_predicate
 					index_elem starts_with_initial expr_or_predicate
+					datetime_template opt_datetime_template
 
 %type	<elems>		accessor_expr
 
@@ -247,9 +249,20 @@ accessor_op:
 	| array_accessor				{ $$ = $1; }
 	| '.' any_path					{ $$ = $2; }
 	| '.' method '(' ')'			{ $$ = makeItemType($2); }
+	| '.' DATETIME_P '(' opt_datetime_template ')'
+									{ $$ = makeItemUnary(jpiDatetime, $4); }
 	| '?' '(' predicate ')'			{ $$ = makeItemUnary(jpiFilter, $3); }
 	;
 
+datetime_template:
+	STRING_P						{ $$ = makeItemString(&$1); }
+	;
+
+opt_datetime_template:
+	datetime_template				{ $$ = $1; }
+	| /* EMPTY */					{ $$ = NULL; }
+	;
+
 key:
 	key_name						{ $$ = makeItemKey(&$1); }
 	;
@@ -272,6 +285,7 @@ key_name:
 	| FLOOR_P
 	| DOUBLE_P
 	| CEILING_P
+	| DATETIME_P
 	| KEYVALUE_P
 	| LAST_P
 	| STARTS_P
diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l
index 2165ffcc254..ced65edb59b 100644
--- a/src/backend/utils/adt/jsonpath_scan.l
+++ b/src/backend/utils/adt/jsonpath_scan.l
@@ -340,6 +340,7 @@ static const JsonPathKeyword keywords[] = {
 	{ 6, false,	STRICT_P,	"strict"},
 	{ 7, false,	CEILING_P,	"ceiling"},
 	{ 7, false,	UNKNOWN_P,	"unknown"},
+	{ 8, false,	DATETIME_P,	"datetime"},
 	{ 8, false,	KEYVALUE_P,	"keyvalue"},
 	{ 10,false, LIKE_REGEX_P, "like_regex"},
 };
diff --git a/src/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index 867e98b690b..2bd283085e3 100644
--- a/src/backend/utils/errcodes.txt
+++ b/src/backend/utils/errcodes.txt
@@ -207,6 +207,7 @@ Section: Class 22 - Data Exception
 2200S    E    ERRCODE_INVALID_XML_COMMENT                                    invalid_xml_comment
 2200T    E    ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION                     invalid_xml_processing_instruction
 22030    E    ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE                        duplicate_json_object_key_value
+22031    E    ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION            invalid_argument_for_json_datetime_function
 22032    E    ERRCODE_INVALID_JSON_TEXT                                      invalid_json_text
 22033    E    ERRCODE_INVALID_SQL_JSON_SUBSCRIPT                             invalid_sql_json_subscript
 22034    E    ERRCODE_MORE_THAN_ONE_SQL_JSON_ITEM                            more_than_one_sql_json_item
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index e6645f139ce..23d3795688c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9332,6 +9332,28 @@
   proname => 'jsonb_path_match', prorettype => 'bool',
   proargtypes => 'jsonb jsonpath jsonb bool', prosrc => 'jsonb_path_match' },
 
+{ oid => '6015', descr => 'jsonpath exists test with timezone',
+  proname => 'jsonb_path_exists_tz', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_exists_tz' },
+{ oid => '6016', descr => 'jsonpath query with timezone',
+  proname => 'jsonb_path_query_tz', provolatile => 's',
+  prorows => '1000', proretset => 't',
+  prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_query_tz' },
+{ oid => '6017', descr => 'jsonpath query wrapped into array with timezone',
+  proname => 'jsonb_path_query_array_tz', provolatile => 's',
+  prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_query_array_tz' },
+{ oid => '6018', descr => 'jsonpath query first item with timezone',
+  proname => 'jsonb_path_query_first_tz', provolatile => 's',
+  prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_query_first_tz' },
+{ oid => '6019', descr => 'jsonpath match with timezone',
+  proname => 'jsonb_path_match_tz', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_match_tz' },
+
 { oid => '4010', descr => 'implementation of @? operator',
   proname => 'jsonb_path_exists_opr', prorettype => 'bool',
   proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_path_exists_opr' },
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 40ad5fda928..f6b17c8aa2f 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -79,6 +79,7 @@ typedef enum JsonPathItemType
 	jpiFloor,					/* .floor() item method */
 	jpiCeiling,					/* .ceiling() item method */
 	jpiDouble,					/* .double() item method */
+	jpiDatetime,				/* .datetime() item method */
 	jpiKeyValue,				/* .keyvalue() item method */
 	jpiSubscript,				/* array subscript: 'expr' or 'expr TO expr' */
 	jpiLast,					/* LAST array subscript */
diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
index 0202667a1f7..f40a97dc4e1 100644
--- a/src/test/regress/expected/jsonb_jsonpath.out
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -1658,6 +1658,532 @@ select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ lik
  "a\b"
 (1 row)
 
+select jsonb_path_query('null', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('true', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('1', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('[]', '$.datetime()');
+ jsonb_path_query 
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'strict $.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('{}', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('""', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  unrecognized datetime format
+HINT:  use datetime template argument for explicit format specification
+select jsonb_path_query('"12:34"', '$.datetime("aaa")');
+ERROR:  invalid datetime format separator: "a"
+select jsonb_path_query('"aaaa"', '$.datetime("HH24")');
+ERROR:  invalid value "aa" for "HH24"
+DETAIL:  Value must be an integer.
+select jsonb '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")';
+ ?column? 
+----------
+ t
+(1 row)
+
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+ jsonb_path_query 
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+ jsonb_path_query 
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+ERROR:  trailing characters remain in input string after datetime format
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+ERROR:  trailing characters remain in input string after datetime format
+select jsonb_path_query('"10-03-2017 12:34"', '       $.datetime("dd-mm-yyyy HH24:MI").type()');
+       jsonb_path_query        
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+      jsonb_path_query      
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+     jsonb_path_query     
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+   jsonb_path_query    
+-----------------------
+ "time with time zone"
+(1 row)
+
+set time zone '+00';
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+ jsonb_path_query 
+------------------
+ "12:34:00"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone '+10';
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+ jsonb_path_query 
+------------------
+ "12:34:00"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone default;
+select jsonb_path_query('"2017-03-10"', '$.datetime().type()');
+ jsonb_path_query 
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"2017-03-10"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+       jsonb_path_query        
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:34:56"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()');
+      jsonb_path_query      
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:56+03:00"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()');
+      jsonb_path_query      
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:56+03:10"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime().type()');
+     jsonb_path_query     
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "12:34:56"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()');
+   jsonb_path_query    
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "12:34:56+03:00"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()');
+   jsonb_path_query    
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "12:34:56+03:10"
+(1 row)
+
+set time zone '+00';
+-- date comparison
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR:  cannot convert value from date to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR:  cannot convert value from date to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR:  cannot convert value from date to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10"
+ "2017-03-10T00:00:00"
+ "2017-03-10T03:00:00+03:00"
+(3 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10"
+ "2017-03-11"
+ "2017-03-10T00:00:00"
+ "2017-03-10T12:34:56"
+ "2017-03-10T03:00:00+03:00"
+(5 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-09"
+ "2017-03-10T01:02:03+04:00"
+(2 rows)
+
+-- time comparison
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00"
+ "12:35:00+00:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00"
+ "12:36:00"
+ "12:35:00+00:00"
+(3 rows)
+
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:34:00"
+ "12:35:00+01:00"
+ "13:35:00+01:00"
+(3 rows)
+
+-- timetz comparison
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00+01:00"
+(1 row)
+
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00+01:00"
+ "12:36:00+01:00"
+ "12:35:00-02:00"
+ "11:35:00"
+ "12:35:00"
+(5 rows)
+
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:34:00+01:00"
+ "12:35:00+02:00"
+ "10:35:00"
+(3 rows)
+
+-- timestamp comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T13:35:00+01:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T12:36:00"
+ "2017-03-10T13:35:00+01:00"
+ "2017-03-10T12:35:00-01:00"
+ "2017-03-11"
+(5 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:34:00"
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10"
+(3 rows)
+
+-- timestamptz comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T11:35:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+set time zone default;
 -- jsonpath operators
 SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
  jsonb_path_query 
diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
index ecdd453942b..f5d5fa4fb8d 100644
--- a/src/test/regress/expected/jsonpath.out
+++ b/src/test/regress/expected/jsonpath.out
@@ -401,6 +401,18 @@ select '$.keyvalue().key'::jsonpath;
  $.keyvalue()."key"
 (1 row)
 
+select '$.datetime()'::jsonpath;
+   jsonpath   
+--------------
+ $.datetime()
+(1 row)
+
+select '$.datetime("datetime template")'::jsonpath;
+            jsonpath             
+---------------------------------
+ $.datetime("datetime template")
+(1 row)
+
 select '$ ? (@ starts with "abc")'::jsonpath;
         jsonpath         
 -------------------------
diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
index e7629fb7f9d..a6cb74798a6 100644
--- a/src/test/regress/sql/jsonb_jsonpath.sql
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -346,6 +346,178 @@ select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ lik
 select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\B$" flag "iq")');
 select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\b$" flag "")');
 
+select jsonb_path_query('null', '$.datetime()');
+select jsonb_path_query('true', '$.datetime()');
+select jsonb_path_query('1', '$.datetime()');
+select jsonb_path_query('[]', '$.datetime()');
+select jsonb_path_query('[]', 'strict $.datetime()');
+select jsonb_path_query('{}', '$.datetime()');
+select jsonb_path_query('""', '$.datetime()');
+select jsonb_path_query('"12:34"', '$.datetime("aaa")');
+select jsonb_path_query('"aaaa"', '$.datetime("HH24")');
+
+select jsonb '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")';
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+
+select jsonb_path_query('"10-03-2017 12:34"', '       $.datetime("dd-mm-yyyy HH24:MI").type()');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+
+set time zone '+00';
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone '+10';
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone default;
+
+select jsonb_path_query('"2017-03-10"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()');
+select jsonb_path_query('"12:34:56"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56"', '$.datetime()');
+select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56 +3"', '$.datetime()');
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()');
+
+set time zone '+00';
+
+-- date comparison
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+
+-- time comparison
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+
+-- timetz comparison
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+
+-- timestamp comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+
+-- timestamptz comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+
+set time zone default;
+
 -- jsonpath operators
 
 SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
index 29ea77a4858..713d32400a7 100644
--- a/src/test/regress/sql/jsonpath.sql
+++ b/src/test/regress/sql/jsonpath.sql
@@ -72,6 +72,8 @@ select '"aaa".type()'::jsonpath;
 select 'true.type()'::jsonpath;
 select '$.double().floor().ceiling().abs()'::jsonpath;
 select '$.keyvalue().key'::jsonpath;
+select '$.datetime()'::jsonpath;
+select '$.datetime("datetime template")'::jsonpath;
 
 select '$ ? (@ starts with "abc")'::jsonpath;
 select '$ ? (@ starts with $var)'::jsonpath;
-- 
2.14.3

#20Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Alexander Korotkov (#19)
6 attachment(s)
Re: Support for jsonpath .datetime() method

On Mon, Sep 16, 2019 at 10:05 PM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

On Sat, Sep 14, 2019 at 10:18 PM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

On Tue, Aug 27, 2019 at 5:19 AM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

Revised patchset is attached. It still requires some polishing. But
the most doubtful part is handling of RR, YYY, YY and Y.

Standard requires us to complete YYY, YY and Y with high digits from
current year. So, if YY matches 99, then year should be 2099, not
1999.

For RR, standard requirements are relaxed. Implementation may choose
matching year from range [current_year - 100; current_year + 100]. It
looks reasonable to handle RR in the same way we currently handle YY:
select appropriate year in [1970; 2069] range. It seems like we
select this range to start in the same point as unix timestamp. But
nowadays it still looks reasonable: it's about +- 50 from current
year. So, years close to the current one are likely completed
correctly. In Oracle RR returns year in [1950; 1949] range. So, it
seems to be designed near 2000 :). I don't think we need to copy this
behavior.

Handling YYY and YY in standard way seems quite easy. We can complete
them as 2YYY and 20YY. This should be standard conforming till 2100.

But handling Y looks problematic. Immutable way of handling this
would work only for decade. Current code completes Y as 200Y and it
looks pretty "outdated" now in 2019. Using current real year would
make conversion timestamp-dependent. This property doesn't look favor
for to_date()/to_timestamp() and unacceptable for immutable jsonpath
functions (but we can forbid using Y pattern there). Current patch
complete Y as 202Y assuming v13 will be released in 2020. But I'm not
sure what is better solution here. The bright side is that I haven't
seen anybody use Y patten in real life :)

Revised patchset is attached. It adds and adjusts commit messages,
comments and does other cosmetic improvements.

I think 0001 and 0002 are well reviewed already. And these patches
are usable not only for jsonpath .datetime(), but contain improvements
for existing to_date()/to_timestamp() SQL functions. I'm going to
push these two if no objections.

Those two patches are pushed. Just before commit I've renamed
deciseconds to "tenths of seconds", sentiseconds to "hundredths of
seconds" as discussed before [1].

The rest of patchset is attached.

I've reordered the patchset. I moved the most debatable patch, which
introduces RRRR and RR and changes parsing of YYY, YY and Y to the
end. I think we have enough of time in this release cycle to decide
whether we want this.

Patches 0001-0005 looks quite mature for me. I'm going to push this
if no objections. After pushing them, I'm going to start discussion
related to RR, YY and friends in separate thread.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0002-Implement-parse_datetime-function-8.patchapplication/octet-stream; name=0002-Implement-parse_datetime-function-8.patchDownload
From 8e773297c9ccfbe364f3c4a626dc983bb387fbe7 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Mon, 26 Aug 2019 05:34:19 +0300
Subject: [PATCH 2/6] Implement parse_datetime() function

This commit adds parse_datetime() function, which implements datetime
parsing with extended features demanded by upcoming jsonpath .datetime()
method:

 * Dynamic type identification based on template string,
 * Support for standard-conforming 'strict' mode,
 * Timezone offset is returned as separate value.

Extracted from original patch by Nikita Glukhov, Teodor Sigaev, Oleg Bartunov.
Revised by me.

Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
Discussion: https://postgr.es/m/CAPpHfdsZgYEra_PeCLGNoXOWYx6iU-S3wF8aX0ObQUcZU%2B4XTw%40mail.gmail.com
Author: Nikita Glukhov, Teodor Sigaev, Oleg Bartunov, Alexander Korotkov
Reviewed-by: Anastasia Lubennikova, Peter Eisentraut
---
 src/backend/utils/adt/date.c       |  11 +-
 src/backend/utils/adt/formatting.c | 293 ++++++++++++++++++++++++++++++++++++-
 src/include/utils/date.h           |   3 +
 src/include/utils/formatting.h     |   3 +
 4 files changed, 298 insertions(+), 12 deletions(-)

diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 4b1afb10f92..9e291b5c7bc 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -41,11 +41,6 @@
 #endif
 
 
-static int	tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
-static int	tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
-static void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
-
-
 /* common code for timetypmodin and timetztypmodin */
 static int32
 anytime_typmodin(bool istz, ArrayType *ta)
@@ -1203,7 +1198,7 @@ time_in(PG_FUNCTION_ARGS)
 /* tm2time()
  * Convert a tm structure to a time data type.
  */
-static int
+int
 tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result)
 {
 	*result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec)
@@ -1379,7 +1374,7 @@ time_scale(PG_FUNCTION_ARGS)
  * have a fundamental tie together but rather a coincidence of
  * implementation. - thomas
  */
-static void
+void
 AdjustTimeForTypmod(TimeADT *time, int32 typmod)
 {
 	static const int64 TimeScales[MAX_TIME_PRECISION + 1] = {
@@ -1957,7 +1952,7 @@ time_part(PG_FUNCTION_ARGS)
 /* tm2timetz()
  * Convert a tm structure to a time data type.
  */
-static int
+int
 tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result)
 {
 	result->time = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) *
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index d2f7666eed6..bafeaf141ef 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -992,6 +992,11 @@ typedef struct NUMProc
 			   *L_currency_symbol;
 } NUMProc;
 
+/* Return flags for DCH_from_char() */
+#define DCH_DATED	0x01
+#define DCH_TIMED	0x02
+#define DCH_ZONED	0x04
+
 /* ----------
  * Functions
  * ----------
@@ -1025,7 +1030,8 @@ static int	from_char_parse_int(int *dest, char **src, FormatNode *node);
 static int	seq_search(char *name, const char *const *array, int type, int max, int *len);
 static int	from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
 static void do_to_timestamp(text *date_txt, text *fmt, bool std,
-							struct pg_tm *tm, fsec_t *fsec, int *fprec);
+							struct pg_tm *tm, fsec_t *fsec, int *fprec,
+							uint32 *flags);
 static char *fill_str(char *str, int c, int max);
 static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
 static char *int_to_roman(int number);
@@ -3517,6 +3523,111 @@ DCH_prevent_counter_overflow(void)
 	}
 }
 
+/* Get mask of date/time/zone components present in format nodes. */
+static int
+DCH_datetime_type(FormatNode *node)
+{
+	FormatNode *n;
+	int			flags = 0;
+
+	for (n = node; n->type != NODE_TYPE_END; n++)
+	{
+		if (n->type != NODE_TYPE_ACTION)
+			continue;
+
+		switch (n->key->id)
+		{
+			case DCH_FX:
+				break;
+			case DCH_A_M:
+			case DCH_P_M:
+			case DCH_a_m:
+			case DCH_p_m:
+			case DCH_AM:
+			case DCH_PM:
+			case DCH_am:
+			case DCH_pm:
+			case DCH_HH:
+			case DCH_HH12:
+			case DCH_HH24:
+			case DCH_MI:
+			case DCH_SS:
+			case DCH_MS:		/* millisecond */
+			case DCH_US:		/* microsecond */
+			case DCH_FF1:
+			case DCH_FF2:
+			case DCH_FF3:
+			case DCH_FF4:
+			case DCH_FF5:
+			case DCH_FF6:
+			case DCH_SSSS:
+				flags |= DCH_TIMED;
+				break;
+			case DCH_tz:
+			case DCH_TZ:
+			case DCH_OF:
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("formatting field \"%s\" is only supported in to_char",
+								n->key->name)));
+				flags |= DCH_ZONED;
+				break;
+			case DCH_TZH:
+			case DCH_TZM:
+				flags |= DCH_ZONED;
+				break;
+			case DCH_A_D:
+			case DCH_B_C:
+			case DCH_a_d:
+			case DCH_b_c:
+			case DCH_AD:
+			case DCH_BC:
+			case DCH_ad:
+			case DCH_bc:
+			case DCH_MONTH:
+			case DCH_Month:
+			case DCH_month:
+			case DCH_MON:
+			case DCH_Mon:
+			case DCH_mon:
+			case DCH_MM:
+			case DCH_DAY:
+			case DCH_Day:
+			case DCH_day:
+			case DCH_DY:
+			case DCH_Dy:
+			case DCH_dy:
+			case DCH_DDD:
+			case DCH_IDDD:
+			case DCH_DD:
+			case DCH_D:
+			case DCH_ID:
+			case DCH_WW:
+			case DCH_Q:
+			case DCH_CC:
+			case DCH_Y_YYY:
+			case DCH_YYYY:
+			case DCH_IYYY:
+			case DCH_RRRR:
+			case DCH_YYY:
+			case DCH_IYY:
+			case DCH_YY:
+			case DCH_IY:
+			case DCH_RR:
+			case DCH_Y:
+			case DCH_I:
+			case DCH_RM:
+			case DCH_rm:
+			case DCH_W:
+			case DCH_J:
+				flags |= DCH_DATED;
+				break;
+		}
+	}
+
+	return flags;
+}
+
 /* select a DCHCacheEntry to hold the given format picture */
 static DCHCacheEntry *
 DCH_cache_getnew(const char *str, bool std)
@@ -3808,7 +3919,7 @@ to_timestamp(PG_FUNCTION_ARGS)
 	fsec_t		fsec;
 	int			fprec;
 
-	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL);
 
 	/* Use the specified time zone, if any. */
 	if (tm.tm_zone)
@@ -3847,7 +3958,7 @@ to_date(PG_FUNCTION_ARGS)
 	struct pg_tm tm;
 	fsec_t		fsec;
 
-	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL);
 
 	/* Prevent overflow in Julian-day routines */
 	if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3868,6 +3979,176 @@ to_date(PG_FUNCTION_ARGS)
 	PG_RETURN_DATEADT(result);
 }
 
+/*
+ * Convert the 'date_txt' input to a datetime type using argument 'fmt' as a format string.
+ * The actual data type (returned in 'typid', 'typmod') is determined by
+ * the presence of date/time/zone components in the format string.
+ *
+ * When timezone component is present, the corresponding offset is set to '*tz'.
+ */
+Datum
+parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
+			   int32 *typmod, int *tz)
+{
+	struct pg_tm tm;
+	fsec_t		fsec;
+	int			fprec = 0;
+	uint32		flags;
+
+	do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags);
+
+	*typmod = fprec ? fprec : -1;	/* fractional part precision */
+
+	if (flags & DCH_DATED)
+	{
+		if (flags & DCH_TIMED)
+		{
+			if (flags & DCH_ZONED)
+			{
+				TimestampTz result;
+
+				if (tm.tm_zone)
+				{
+					int			dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
+
+					if (dterr)
+						DateTimeParseError(dterr, text_to_cstring(date_txt), "timestamptz");
+				}
+				else
+				{
+					/*
+					 * Time zone is present in format string, but not in input
+					 * string.  Assuming do_to_timestamp() triggers no error
+					 * this should be possible only in non-strict case.
+					 */
+					Assert(!strict);
+
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							 errmsg("missing time zone in input string for type timestamptz")));
+				}
+
+				if (tm2timestamp(&tm, fsec, tz, &result) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("timestamptz out of range")));
+
+				AdjustTimestampForTypmod(&result, *typmod);
+
+				*typid = TIMESTAMPTZOID;
+				return TimestampTzGetDatum(result);
+			}
+			else
+			{
+				Timestamp	result;
+
+				if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("timestamp out of range")));
+
+				AdjustTimestampForTypmod(&result, *typmod);
+
+				*typid = TIMESTAMPOID;
+				return TimestampGetDatum(result);
+			}
+		}
+		else
+		{
+			if (flags & DCH_ZONED)
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+						 errmsg("datetime format is zoned but not timed")));
+			}
+			else
+			{
+				DateADT		result;
+
+				/* Prevent overflow in Julian-day routines */
+				if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("date out of range: \"%s\"",
+									text_to_cstring(date_txt))));
+
+				result = 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(result))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							 errmsg("date out of range: \"%s\"",
+									text_to_cstring(date_txt))));
+
+				*typid = DATEOID;
+				return DateADTGetDatum(result);
+			}
+		}
+	}
+	else if (flags & DCH_TIMED)
+	{
+		if (flags & DCH_ZONED)
+		{
+			TimeTzADT  *result = palloc(sizeof(TimeTzADT));
+
+			if (tm.tm_zone)
+			{
+				int			dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
+
+				if (dterr)
+					DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz");
+			}
+			else
+			{
+				/*
+				 * Time zone is present in format string, but not in input
+				 * string.  Assuming do_to_timestamp() triggers no error this
+				 * should be possible only in non-strict case.
+				 */
+				Assert(!strict);
+
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+						 errmsg("missing time zone in input string for type timetz")));
+			}
+
+			if (tm2timetz(&tm, fsec, *tz, result) != 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("timetz out of range")));
+
+			AdjustTimeForTypmod(&result->time, *typmod);
+
+			*typid = TIMETZOID;
+			return TimeTzADTPGetDatum(result);
+		}
+		else
+		{
+			TimeADT		result;
+
+			if (tm2time(&tm, fsec, &result) != 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("time out of range")));
+
+			AdjustTimeForTypmod(&result, *typmod);
+
+			*typid = TIMEOID;
+			return TimeADTGetDatum(result);
+		}
+	}
+	else
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+				 errmsg("datetime format is not dated and not timed")));
+	}
+
+	return (Datum) 0;
+}
+
 /*
  * do_to_timestamp: shared code for to_timestamp and to_date
  *
@@ -3883,7 +4164,8 @@ to_date(PG_FUNCTION_ARGS)
  */
 static void
 do_to_timestamp(text *date_txt, text *fmt, bool std,
-				struct pg_tm *tm, fsec_t *fsec, int *fprec)
+				struct pg_tm *tm, fsec_t *fsec, int *fprec,
+				uint32 *flags)
 {
 	FormatNode *format;
 	TmFromChar	tmfc;
@@ -3940,6 +4222,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 
 		pfree(fmt_str);
 
+		if (flags)
+			*flags = DCH_datetime_type(format);
+
 		if (!incache)
 			pfree(format);
 	}
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index bec129aff1c..bd15bfa5bb0 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -76,5 +76,8 @@ extern TimeTzADT *GetSQLCurrentTime(int32 typmod);
 extern TimeADT GetSQLLocalTime(int32 typmod);
 extern int	time2tm(TimeADT time, struct pg_tm *tm, fsec_t *fsec);
 extern int	timetz2tm(TimeTzADT *time, struct pg_tm *tm, fsec_t *fsec, int *tzp);
+extern int	tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
+extern int	tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
+extern void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
 
 #endif							/* DATE_H */
diff --git a/src/include/utils/formatting.h b/src/include/utils/formatting.h
index 0117144779e..beeaf10c332 100644
--- a/src/include/utils/formatting.h
+++ b/src/include/utils/formatting.h
@@ -26,4 +26,7 @@ extern char *asc_tolower(const char *buff, size_t nbytes);
 extern char *asc_toupper(const char *buff, size_t nbytes);
 extern char *asc_initcap(const char *buff, size_t nbytes);
 
+extern Datum parse_datetime(text *date_txt, text *fmt, bool std,
+							Oid *typid, int32 *typmod, int *tz);
+
 #endif
-- 
2.14.3

0005-Implement-jsonpath-.datetime-method-8.patchapplication/octet-stream; name=0005-Implement-jsonpath-.datetime-method-8.patchDownload
From afbedbe1790ee18cd3f362c37448e9e646885f65 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Sun, 21 Jul 2019 01:16:38 +0300
Subject: [PATCH 5/6] Implement jsonpath .datetime() method

This commit implements jsonpath .datetime() method as it's specified in
SQL/JSON standard.  There are no-argument and single-argument versions of
this method.  No-argument version selects first of ISO datetime formats
matching input string.  Single-argument version accepts template string as
its argument.

Additionally to .datetime() method itself this commit also implements
comparison ability of resulting date and time values.  There is some difficulty
because exising jsonb_path_*() functions are immutable, while comparison of
timezoned and non-timezoned types involves current timezone.  At first, current
timezone could be changes in session.  Moreover, timezones themselves are not
immutable and could be updated.  This is why we let existing immutable functions
throw errors on such non-immutable comparison.  In the same time this commit
provides jsonb_path_*_tz() functions which are stable and support operations
involving timezones.

Support of .datetime() method was the only blocker prevents T832 from being
marked as supported.  sql_features.txt is updated correspondingly.

Extracted from original patch by Nikita Glukhov, Teodor Sigaev, Oleg Bartunov.
Heavily revised by me.  Comments were adjusted by Liudmila Mantrova.

Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
Discussion: https://postgr.es/m/CAPpHfdsZgYEra_PeCLGNoXOWYx6iU-S3wF8aX0ObQUcZU%2B4XTw%40mail.gmail.com
Author: Alexander Korotkov, Nikita Glukhov, Teodor Sigaev, Oleg Bartunov, Liudmila Mantrova
Reviewed-by: Anastasia Lubennikova, Peter Eisentraut
---
 doc/src/sgml/func.sgml                       | 106 +++++-
 src/backend/catalog/sql_features.txt         |   2 +-
 src/backend/catalog/system_views.sql         |  40 ++
 src/backend/utils/adt/jsonpath.c             |  24 +-
 src/backend/utils/adt/jsonpath_exec.c        | 472 ++++++++++++++++++++++--
 src/backend/utils/adt/jsonpath_gram.y        |  14 +
 src/backend/utils/adt/jsonpath_scan.l        |   1 +
 src/backend/utils/errcodes.txt               |   1 +
 src/include/catalog/pg_proc.dat              |  22 ++
 src/include/utils/jsonpath.h                 |   1 +
 src/test/regress/expected/jsonb_jsonpath.out | 526 +++++++++++++++++++++++++++
 src/test/regress/expected/jsonpath.out       |  12 +
 src/test/regress/sql/jsonb_jsonpath.sql      | 172 +++++++++
 src/test/regress/sql/jsonpath.sql            |   2 +
 14 files changed, 1351 insertions(+), 44 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 3d3d9d91836..6a1ee30c494 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -11910,16 +11910,6 @@ table2-mapping
   </para>
 
   <itemizedlist>
-   <listitem>
-    <para>
-     <literal>.datetime()</literal> item method is not implemented yet
-     mainly because immutable <type>jsonpath</type> functions and operators
-     cannot reference session timezone, which is used in some datetime
-     operations.  Datetime support will be added to <type>jsonpath</type>
-     in future versions of <productname>PostgreSQL</productname>.
-    </para>
-   </listitem>
-
    <listitem>
     <para>
      A path expression can be a Boolean predicate, although the SQL/JSON
@@ -12190,6 +12180,20 @@ table2-mapping
         <entry><literal>$.z.abs()</literal></entry>
         <entry><literal>0.3</literal></entry>
        </row>
+       <row>
+        <entry><literal>datetime()</literal></entry>
+        <entry>Date/time value converted from a string</entry>
+        <entry><literal>["2015-8-1", "2015-08-12"]</literal></entry>
+        <entry><literal>$[*] ? (@.datetime() &lt; "2015-08-2". datetime())</literal></entry>
+        <entry><literal>2015-8-1</literal></entry>
+       </row>
+       <row>
+        <entry><literal>datetime(<replaceable>template</replaceable>)</literal></entry>
+        <entry>Date/time value converted from a string using the specified template</entry>
+        <entry><literal>["12:30", "18:40"]</literal></entry>
+        <entry><literal>$[*].datetime("HH24:MI")</literal></entry>
+        <entry><literal>"12:30:00", "18:40:00"</literal></entry>
+       </row>
        <row>
         <entry><literal>keyvalue()</literal></entry>
         <entry>
@@ -12207,6 +12211,26 @@ table2-mapping
      </tgroup>
     </table>
 
+    <note>
+     <para>
+      The result type of <literal>datetime()</literal> and
+      <literal>datetime(<replaceable>template</replaceable>)</literal>
+      methods can be <type>date</type>, <type>timetz</type>, <type>time</type>,
+      <type>timestamptz</type>, or <type>timestamp</type>.
+      Both methods determine the result type dynamically.
+     </para>
+     <para>
+      The <literal>datetime()</literal> method sequentially tries ISO formats
+      for <type>date</type>, <type>timetz</type>, <type>time</type>,
+      <type>timestamptz</type>, and <type>timestamp</type>. It stops on
+      the first matching format and the corresponding data type.
+     </para>
+     <para>
+      The <literal>datetime(<replaceable>template</replaceable>)</literal>
+      method determines the result type by the provided template string.
+     </para>
+    </note>
+
     <table id="functions-sqljson-filter-ex-table">
      <title><type>jsonpath</type> Filter Expression Elements</title>
      <tgroup cols="5">
@@ -12350,6 +12374,15 @@ table2-mapping
       </tbody>
      </tgroup>
     </table>
+
+    <note>
+     <para>
+      When different date/time values are compared, an implicit cast is
+      applied. A <type>date</type> value can be cast to <type>timestamp</type>
+      or <type>timestamptz</type>, <type>timestamp</type> can be cast to
+      <type>timestamptz</type>, and <type>time</type> &mdash; to <type>timetz</type>.
+     </para>
+    </note>
    </sect3>
 
   </sect2>
@@ -12582,7 +12615,7 @@ table2-mapping
    <para>
     The <literal>@?</literal> and <literal>@@</literal> operators suppress
     the following errors: lacking object field or array element, unexpected
-    JSON item type, and numeric errors.
+    JSON item type, datetime and numeric errors.
     This behavior might be helpful while searching over JSON document
     collections of varying structure.
    </para>
@@ -12851,18 +12884,33 @@ table2-mapping
   <indexterm>
    <primary>jsonb_path_exists</primary>
   </indexterm>
+  <indexterm>
+   <primary>jsonb_path_exists_tz</primary>
+  </indexterm>
   <indexterm>
    <primary>jsonb_path_match</primary>
   </indexterm>
+  <indexterm>
+   <primary>jsonb_path_match_tz</primary>
+  </indexterm>
   <indexterm>
    <primary>jsonb_path_query</primary>
   </indexterm>
+  <indexterm>
+   <primary>jsonb_path_query_tz</primary>
+  </indexterm>
   <indexterm>
    <primary>jsonb_path_query_array</primary>
   </indexterm>
+  <indexterm>
+   <primary>jsonb_path_query_array_tz</primary>
+  </indexterm>
   <indexterm>
    <primary>jsonb_path_query_first</primary>
   </indexterm>
+  <indexterm>
+   <primary>jsonb_path_query_first_tz</primary>
+  </indexterm>
 
   <table id="functions-json-processing-table">
     <title>JSON Processing Functions</title>
@@ -13202,6 +13250,9 @@ table2-mapping
          <para><literal>
            jsonb_path_exists(target jsonb, path jsonpath [, vars jsonb [, silent bool]])
          </literal></para>
+         <para><literal>
+           jsonb_path_exists_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>boolean</type></entry>
         <entry>
@@ -13222,6 +13273,9 @@ table2-mapping
          <para><literal>
            jsonb_path_match(target jsonb, path jsonpath [, vars jsonb, silent bool])
          </literal></para>
+         <para><literal>
+           jsonb_path_match_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>boolean</type></entry>
         <entry>
@@ -13243,6 +13297,9 @@ table2-mapping
          <para><literal>
            jsonb_path_query(target jsonb, path jsonpath [, vars jsonb, silent bool])
          </literal></para>
+         <para><literal>
+           jsonb_path_query_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>setof jsonb</type></entry>
         <entry>
@@ -13271,6 +13328,9 @@ table2-mapping
          <para><literal>
            jsonb_path_query_array(target jsonb, path jsonpath [, vars jsonb, silent bool])
          </literal></para>
+         <para><literal>
+           jsonb_path_query_array_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>jsonb</type></entry>
         <entry>
@@ -13291,6 +13351,9 @@ table2-mapping
          <para><literal>
            jsonb_path_query_first(target jsonb, path jsonpath [, vars jsonb, silent bool])
          </literal></para>
+         <para><literal>
+           jsonb_path_query_first_tz(target jsonb, path jsonpath [, vars jsonb, silent bool])
+         </literal></para>
         </entry>
         <entry><type>jsonb</type></entry>
         <entry>
@@ -13433,11 +13496,8 @@ table2-mapping
 
   <note>
    <para>
-    The <literal>jsonb_path_exists</literal>, <literal>jsonb_path_match</literal>,
-    <literal>jsonb_path_query</literal>, <literal>jsonb_path_query_array</literal>, and
-    <literal>jsonb_path_query_first</literal>
-    functions have optional <literal>vars</literal> and <literal>silent</literal>
-    arguments.
+    The <literal>jsonb_path_*</literal> functions have optional
+    <literal>vars</literal> and <literal>silent</literal> arguments.
    </para>
    <para>
     If the <parameter>vars</parameter> argument is specified, it provides an
@@ -13451,6 +13511,20 @@ table2-mapping
    </para>
   </note>
 
+  <note>
+   <para>
+    Some of the <literal>jsonb_path_*</literal> functions have the
+    <literal>_tz</literal> suffix. These functions have been implemented to
+    support comparison of date/time values that involves implicit
+    timezone-aware casts. Since operations with time zones are not immutable,
+    these functions are qualified as stable. Their counterparts without the
+    suffix do not support such casts, so they are immutable and can be used for
+    such use-cases as expression indexes
+    (see <xref linkend="indexes-expressional"/>). There is no difference
+    between these functions for other <type>jsonpath</type> operations.
+   </para>
+  </note>
+
   <para>
     See also <xref linkend="functions-aggregate"/> for the aggregate
     function <function>json_agg</function> which aggregates record
diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index 059ec02cd03..9c68292a54d 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -544,7 +544,7 @@ T828	JSON_QUERY			NO
 T829	JSON_QUERY: array wrapper options			NO	
 T830	Enforcing unique keys in SQL/JSON constructor functions			NO	
 T831	SQL/JSON path language: strict mode			YES	
-T832	SQL/JSON path language: item method			NO	datetime() not yet implemented
+T832	SQL/JSON path language: item method			YES	
 T833	SQL/JSON path language: multiple subscripts			YES	
 T834	SQL/JSON path language: wildcard member accessor			YES	
 T835	SQL/JSON path language: filter expressions			YES	
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ea4c85e3959..423ae4fdc8a 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1287,6 +1287,46 @@ LANGUAGE INTERNAL
 STRICT IMMUTABLE PARALLEL SAFE
 AS 'jsonb_path_query_first';
 
+CREATE OR REPLACE FUNCTION
+  jsonb_path_exists_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                    silent boolean DEFAULT false)
+RETURNS boolean
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_exists_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_match_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                   silent boolean DEFAULT false)
+RETURNS boolean
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_match_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_query_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                   silent boolean DEFAULT false)
+RETURNS SETOF jsonb
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_query_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_query_array_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                         silent boolean DEFAULT false)
+RETURNS jsonb
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_query_array_tz';
+
+CREATE OR REPLACE FUNCTION
+  jsonb_path_query_first_tz(target jsonb, path jsonpath, vars jsonb DEFAULT '{}',
+                         silent boolean DEFAULT false)
+RETURNS jsonb
+LANGUAGE INTERNAL
+STRICT IMMUTABLE PARALLEL SAFE
+AS 'jsonb_path_query_first_tz';
+
 --
 -- The default permissions for functions mean that anyone can execute them.
 -- A number of functions shouldn't be executable by just anyone, but rather
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 7f322485e7b..e683cbef7c6 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -337,12 +337,14 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
 		case jpiPlus:
 		case jpiMinus:
 		case jpiExists:
+		case jpiDatetime:
 			{
 				int32		arg = reserveSpaceForItemPointer(buf);
 
-				chld = flattenJsonPathParseItem(buf, item->value.arg,
-												nestingLevel + argNestingLevel,
-												insideArraySubscript);
+				chld = !item->value.arg ? pos :
+					flattenJsonPathParseItem(buf, item->value.arg,
+											 nestingLevel + argNestingLevel,
+											 insideArraySubscript);
 				*(int32 *) (buf->data + arg) = chld - pos;
 			}
 			break;
@@ -692,6 +694,15 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
 		case jpiDouble:
 			appendBinaryStringInfo(buf, ".double()", 9);
 			break;
+		case jpiDatetime:
+			appendBinaryStringInfo(buf, ".datetime(", 10);
+			if (v->content.arg)
+			{
+				jspGetArg(v, &elem);
+				printJsonPathItem(buf, &elem, false, false);
+			}
+			appendStringInfoChar(buf, ')');
+			break;
 		case jpiKeyValue:
 			appendBinaryStringInfo(buf, ".keyvalue()", 11);
 			break;
@@ -754,6 +765,8 @@ jspOperationName(JsonPathItemType type)
 			return "floor";
 		case jpiCeiling:
 			return "ceiling";
+		case jpiDatetime:
+			return "datetime";
 		default:
 			elog(ERROR, "unrecognized jsonpath item type: %d", type);
 			return NULL;
@@ -889,6 +902,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
 		case jpiPlus:
 		case jpiMinus:
 		case jpiFilter:
+		case jpiDatetime:
 			read_int32(v->content.arg, base, pos);
 			break;
 		case jpiIndexArray:
@@ -913,7 +927,8 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a)
 		   v->type == jpiIsUnknown ||
 		   v->type == jpiExists ||
 		   v->type == jpiPlus ||
-		   v->type == jpiMinus);
+		   v->type == jpiMinus ||
+		   v->type == jpiDatetime);
 
 	jspInitByBuffer(a, v->base, v->content.arg);
 }
@@ -961,6 +976,7 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
 			   v->type == jpiFloor ||
 			   v->type == jpiCeiling ||
 			   v->type == jpiDouble ||
+			   v->type == jpiDatetime ||
 			   v->type == jpiKeyValue ||
 			   v->type == jpiStartsWith);
 
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index 565b00c4266..fe47fb11ace 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -66,6 +66,7 @@
 #include "miscadmin.h"
 #include "regex/regex.h"
 #include "utils/builtins.h"
+#include "utils/datetime.h"
 #include "utils/datum.h"
 #include "utils/formatting.h"
 #include "utils/float.h"
@@ -107,6 +108,7 @@ typedef struct JsonPathExecContext
 										 * ignored */
 	bool		throwErrors;	/* with "false" all suppressible errors are
 								 * suppressed */
+	bool		useTz;
 } JsonPathExecContext;
 
 /* Context for LIKE_REGEX execution. */
@@ -173,7 +175,8 @@ typedef JsonPathBool (*JsonPathPredicateCallback) (JsonPathItem *jsp,
 typedef Numeric (*BinaryArithmFunc) (Numeric num1, Numeric num2, bool *error);
 
 static JsonPathExecResult executeJsonPath(JsonPath *path, Jsonb *vars,
-										  Jsonb *json, bool throwErrors, JsonValueList *result);
+										  Jsonb *json, bool throwErrors,
+										  JsonValueList *result, bool useTz);
 static JsonPathExecResult executeItem(JsonPathExecContext *cxt,
 									  JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
 static JsonPathExecResult executeItemOptUnwrapTarget(JsonPathExecContext *cxt,
@@ -214,6 +217,8 @@ static JsonPathBool executeLikeRegex(JsonPathItem *jsp, JsonbValue *str,
 static JsonPathExecResult executeNumericItemMethod(JsonPathExecContext *cxt,
 												   JsonPathItem *jsp, JsonbValue *jb, bool unwrap, PGFunction func,
 												   JsonValueList *found);
+static JsonPathExecResult executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
+												JsonbValue *jb, JsonValueList *found);
 static JsonPathExecResult executeKeyValueMethod(JsonPathExecContext *cxt,
 												JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
 static JsonPathExecResult appendBoolResult(JsonPathExecContext *cxt,
@@ -225,7 +230,8 @@ static void getJsonPathVariable(JsonPathExecContext *cxt,
 static int	JsonbArraySize(JsonbValue *jb);
 static JsonPathBool executeComparison(JsonPathItem *cmp, JsonbValue *lv,
 									  JsonbValue *rv, void *p);
-static JsonPathBool compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2);
+static JsonPathBool compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2,
+								 bool useTz);
 static int	compareNumeric(Numeric a, Numeric b);
 static JsonbValue *copyJsonbValue(JsonbValue *src);
 static JsonPathExecResult getArrayIndex(JsonPathExecContext *cxt,
@@ -246,6 +252,8 @@ static JsonbValue *JsonbInitBinary(JsonbValue *jbv, Jsonb *jb);
 static int	JsonbType(JsonbValue *jb);
 static JsonbValue *getScalar(JsonbValue *scalar, enum jbvType type);
 static JsonbValue *wrapItemsInArray(const JsonValueList *items);
+static int	compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
+							bool useTz, bool *have_error);
 
 /****************** User interface to JsonPath executor ********************/
 
@@ -261,8 +269,8 @@ static JsonbValue *wrapItemsInArray(const JsonValueList *items);
  *		SQL/JSON.  Regarding jsonb_path_match(), this function doesn't have
  *		an analogy in SQL/JSON, so we define its behavior on our own.
  */
-Datum
-jsonb_path_exists(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_exists_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -276,7 +284,7 @@ jsonb_path_exists(PG_FUNCTION_ARGS)
 		silent = PG_GETARG_BOOL(3);
 	}
 
-	res = executeJsonPath(jp, vars, jb, !silent, NULL);
+	res = executeJsonPath(jp, vars, jb, !silent, NULL, tz);
 
 	PG_FREE_IF_COPY(jb, 0);
 	PG_FREE_IF_COPY(jp, 1);
@@ -287,6 +295,18 @@ jsonb_path_exists(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(res == jperOk);
 }
 
+Datum
+jsonb_path_exists(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_exists_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_exists_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_exists_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_exists_opr
  *		Implementation of operator "jsonb @? jsonpath" (2-argument version of
@@ -296,7 +316,7 @@ Datum
 jsonb_path_exists_opr(PG_FUNCTION_ARGS)
 {
 	/* just call the other one -- it can handle both cases */
-	return jsonb_path_exists(fcinfo);
+	return jsonb_path_exists_internal(fcinfo, false);
 }
 
 /*
@@ -304,8 +324,8 @@ jsonb_path_exists_opr(PG_FUNCTION_ARGS)
  *		Returns jsonpath predicate result item for the specified jsonb value.
  *		See jsonb_path_exists() comment for details regarding error handling.
  */
-Datum
-jsonb_path_match(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_match_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -319,7 +339,7 @@ jsonb_path_match(PG_FUNCTION_ARGS)
 		silent = PG_GETARG_BOOL(3);
 	}
 
-	(void) executeJsonPath(jp, vars, jb, !silent, &found);
+	(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 	PG_FREE_IF_COPY(jb, 0);
 	PG_FREE_IF_COPY(jp, 1);
@@ -343,6 +363,18 @@ jsonb_path_match(PG_FUNCTION_ARGS)
 	PG_RETURN_NULL();
 }
 
+Datum
+jsonb_path_match(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_match_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_match_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_match_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_match_opr
  *		Implementation of operator "jsonb @@ jsonpath" (2-argument version of
@@ -352,7 +384,7 @@ Datum
 jsonb_path_match_opr(PG_FUNCTION_ARGS)
 {
 	/* just call the other one -- it can handle both cases */
-	return jsonb_path_match(fcinfo);
+	return jsonb_path_match_internal(fcinfo, false);
 }
 
 /*
@@ -360,8 +392,8 @@ jsonb_path_match_opr(PG_FUNCTION_ARGS)
  *		Executes jsonpath for given jsonb document and returns result as
  *		rowset.
  */
-Datum
-jsonb_path_query(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_query_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	FuncCallContext *funcctx;
 	List	   *found;
@@ -385,7 +417,7 @@ jsonb_path_query(PG_FUNCTION_ARGS)
 		vars = PG_GETARG_JSONB_P_COPY(2);
 		silent = PG_GETARG_BOOL(3);
 
-		(void) executeJsonPath(jp, vars, jb, !silent, &found);
+		(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 		funcctx->user_fctx = JsonValueListGetList(&found);
 
@@ -406,13 +438,25 @@ jsonb_path_query(PG_FUNCTION_ARGS)
 	SRF_RETURN_NEXT(funcctx, JsonbPGetDatum(JsonbValueToJsonb(v)));
 }
 
+Datum
+jsonb_path_query(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_query_array
  *		Executes jsonpath for given jsonb document and returns result as
  *		jsonb array.
  */
-Datum
-jsonb_path_query_array(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_query_array_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -420,18 +464,30 @@ jsonb_path_query_array(PG_FUNCTION_ARGS)
 	Jsonb	   *vars = PG_GETARG_JSONB_P(2);
 	bool		silent = PG_GETARG_BOOL(3);
 
-	(void) executeJsonPath(jp, vars, jb, !silent, &found);
+	(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 	PG_RETURN_JSONB_P(JsonbValueToJsonb(wrapItemsInArray(&found)));
 }
 
+Datum
+jsonb_path_query_array(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_array_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_array_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_array_internal(fcinfo, true);
+}
+
 /*
  * jsonb_path_query_first
  *		Executes jsonpath for given jsonb document and returns first result
  *		item.  If there are no items, NULL returned.
  */
-Datum
-jsonb_path_query_first(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_query_first_internal(FunctionCallInfo fcinfo, bool tz)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	JsonPath   *jp = PG_GETARG_JSONPATH_P(1);
@@ -439,7 +495,7 @@ jsonb_path_query_first(PG_FUNCTION_ARGS)
 	Jsonb	   *vars = PG_GETARG_JSONB_P(2);
 	bool		silent = PG_GETARG_BOOL(3);
 
-	(void) executeJsonPath(jp, vars, jb, !silent, &found);
+	(void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
 
 	if (JsonValueListLength(&found) >= 1)
 		PG_RETURN_JSONB_P(JsonbValueToJsonb(JsonValueListHead(&found)));
@@ -447,6 +503,18 @@ jsonb_path_query_first(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 }
 
+Datum
+jsonb_path_query_first(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_first_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_first_tz(PG_FUNCTION_ARGS)
+{
+	return jsonb_path_query_first_internal(fcinfo, true);
+}
+
 /********************Execute functions for JsonPath**************************/
 
 /*
@@ -470,7 +538,7 @@ jsonb_path_query_first(PG_FUNCTION_ARGS)
  */
 static JsonPathExecResult
 executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors,
-				JsonValueList *result)
+				JsonValueList *result, bool useTz)
 {
 	JsonPathExecContext cxt;
 	JsonPathExecResult res;
@@ -500,6 +568,7 @@ executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors,
 	cxt.lastGeneratedObjectId = vars ? 2 : 1;
 	cxt.innermostArraySize = -1;
 	cxt.throwErrors = throwErrors;
+	cxt.useTz = useTz;
 
 	if (jspStrictAbsenseOfErrors(&cxt) && !result)
 	{
@@ -1028,6 +1097,12 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
 			}
 			break;
 
+		case jpiDatetime:
+			if (unwrap && JsonbType(jb) == jbvArray)
+				return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
+
+			return executeDateTimeMethod(cxt, jsp, jb, found);
+
 		case jpiKeyValue:
 			if (unwrap && JsonbType(jb) == jbvArray)
 				return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
@@ -1214,7 +1289,7 @@ executeBoolItem(JsonPathExecContext *cxt, JsonPathItem *jsp,
 			jspGetLeftArg(jsp, &larg);
 			jspGetRightArg(jsp, &rarg);
 			return executePredicate(cxt, jsp, &larg, &rarg, jb, true,
-									executeComparison, NULL);
+									executeComparison, cxt);
 
 		case jpiStartsWith:		/* 'whole STARTS WITH initial' */
 			jspGetLeftArg(jsp, &larg);	/* 'whole' */
@@ -1693,6 +1768,138 @@ executeNumericItemMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
 	return executeNextItem(cxt, jsp, &next, jb, found, false);
 }
 
+/*
+ * Implementation of the .datetime() method.
+ *
+ * Converts a string into a date/time value. The actual type is determined at run time.
+ * If an argument is provided, this argument is used as a template string.
+ * Otherwise, the first fitting ISO format is selected.
+ */
+static JsonPathExecResult
+executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
+					  JsonbValue *jb, JsonValueList *found)
+{
+	JsonbValue	jbvbuf;
+	Datum		value;
+	text	   *datetime;
+	Oid			typid;
+	int32		typmod = -1;
+	int			tz = 0;
+	bool		hasNext;
+	JsonPathExecResult res = jperNotFound;
+	JsonPathItem elem;
+
+	if (!(jb = getScalar(jb, jbvString)))
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+							  errmsg("invalid argument for SQL/JSON datetime function"),
+							  errdetail("jsonpath item method .%s() can only be applied to a string",
+										jspOperationName(jsp->type)))));
+
+	datetime = cstring_to_text_with_len(jb->val.string.val,
+										jb->val.string.len);
+
+	if (jsp->content.arg)
+	{
+		text	   *template;
+		char	   *template_str;
+		int			template_len;
+		bool		have_error = false;
+
+		jspGetArg(jsp, &elem);
+
+		if (elem.type != jpiString)
+			elog(ERROR, "invalid jsonpath item type for .datetime() argument");
+
+		template_str = jspGetString(&elem, &template_len);
+
+		template = cstring_to_text_with_len(template_str,
+											template_len);
+
+		value = parse_datetime(datetime, template, true,
+							   &typid, &typmod, &tz,
+							   jspThrowErrors(cxt) ? NULL : &have_error);
+
+		if (have_error)
+			res = jperError;
+		else
+			res = jperOk;
+	}
+	else
+	{
+		/*
+		 * According to SQL/JSON standard enumerate ISO formats for: date,
+		 * timetz, time, timestamptz, timestamp.
+		 */
+		static const char *fmt_str[] =
+		{
+			"yyyy-mm-dd",
+			"HH24:MI:SS TZH:TZM",
+			"HH24:MI:SS TZH",
+			"HH24:MI:SS",
+			"yyyy-mm-dd HH24:MI:SS TZH:TZM",
+			"yyyy-mm-dd HH24:MI:SS TZH",
+			"yyyy-mm-dd HH24:MI:SS"
+		};
+
+		/* cache for format texts */
+		static text *fmt_txt[lengthof(fmt_str)] = {0};
+		int			i;
+
+		/* loop until datetime format fits */
+		for (i = 0; i < lengthof(fmt_str); i++)
+		{
+			bool		have_error = false;
+
+			if (!fmt_txt[i])
+			{
+				MemoryContext oldcxt =
+				MemoryContextSwitchTo(TopMemoryContext);
+
+				fmt_txt[i] = cstring_to_text(fmt_str[i]);
+				MemoryContextSwitchTo(oldcxt);
+			}
+
+			value = parse_datetime(datetime, fmt_txt[i], true,
+								   &typid, &typmod, &tz,
+								   &have_error);
+
+			if (!have_error)
+			{
+				res = jperOk;
+				break;
+			}
+		}
+
+		if (res == jperNotFound)
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+								  errmsg("invalid argument for SQL/JSON datetime function"),
+								  errdetail("unrecognized datetime format"),
+								  errhint("use datetime template argument for explicit format specification"))));
+	}
+
+	pfree(datetime);
+
+	if (jperIsError(res))
+		return res;
+
+	hasNext = jspGetNext(jsp, &elem);
+
+	if (!hasNext && !found)
+		return res;
+
+	jb = hasNext ? &jbvbuf : palloc(sizeof(*jb));
+
+	jb->type = jbvDatetime;
+	jb->val.datetime.value = value;
+	jb->val.datetime.typid = typid;
+	jb->val.datetime.typmod = typmod;
+	jb->val.datetime.tz = tz;
+
+	return executeNextItem(cxt, jsp, &elem, jb, found, hasNext);
+}
+
 /*
  * Implementation of .keyvalue() method.
  *
@@ -1953,7 +2160,9 @@ JsonbArraySize(JsonbValue *jb)
 static JsonPathBool
 executeComparison(JsonPathItem *cmp, JsonbValue *lv, JsonbValue *rv, void *p)
 {
-	return compareItems(cmp->type, lv, rv);
+	JsonPathExecContext *cxt = (JsonPathExecContext *) p;
+
+	return compareItems(cmp->type, lv, rv, cxt->useTz);
 }
 
 /*
@@ -2048,7 +2257,7 @@ compareStrings(const char *mbstr1, int mblen1,
  * Compare two SQL/JSON items using comparison operation 'op'.
  */
 static JsonPathBool
-compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2)
+compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2, bool useTz)
 {
 	int			cmp;
 	bool		res;
@@ -2089,6 +2298,21 @@ compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2)
 			cmp = compareStrings(jb1->val.string.val, jb1->val.string.len,
 								 jb2->val.string.val, jb2->val.string.len);
 			break;
+		case jbvDatetime:
+			{
+				bool		have_error = false;
+
+				cmp = compareDatetime(jb1->val.datetime.value,
+									  jb1->val.datetime.typid,
+									  jb2->val.datetime.value,
+									  jb2->val.datetime.typid,
+									  useTz,
+									  &have_error);
+
+				if (have_error)
+					return jpbUnknown;
+			}
+			break;
 
 		case jbvBinary:
 		case jbvArray:
@@ -2348,3 +2572,205 @@ wrapItemsInArray(const JsonValueList *items)
 
 	return pushJsonbValue(&ps, WJB_END_ARRAY, NULL);
 }
+
+/*
+ * Cross-type comparison of two datetime SQL/JSON items.  If items are
+ * uncomparable, 'error' flag is set.
+ */
+static int
+compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
+				bool useTz, bool *have_error)
+{
+	PGFunction cmpfunc = NULL;
+
+	switch (typid1)
+	{
+		case DATEOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					cmpfunc = date_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					val1 = date2timestamp_opt_error(val1, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"date", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val1 = date2timestamptz_opt_error(val1, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMEOID:
+			switch (typid2)
+			{
+				case TIMEOID:
+					cmpfunc = time_cmp;
+
+					break;
+
+				case TIMETZOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"time", "timetz"),
+								 errhint("use *_tz() function for timezone support")));
+					val1 = DirectFunctionCall1(time_timetz, val1);
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case DATEOID:
+				case TIMESTAMPOID:
+				case TIMESTAMPTZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMETZOID:
+			switch (typid2)
+			{
+				case TIMEOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"time", "timetz"),
+								 errhint("use *_tz() function for timezone support")));
+					val2 = DirectFunctionCall1(time_timetz, val2);
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case TIMETZOID:
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case DATEOID:
+				case TIMESTAMPOID:
+				case TIMESTAMPTZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMESTAMPOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					val2 = date2timestamp_opt_error(val2, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"timestamp", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val1 = timestamp2timestamptz_opt_error(val1, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		case TIMESTAMPTZOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"date", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val2 = date2timestamptz_opt_error(val2, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					if (!useTz)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot convert value from %s to %s without timezone usage",
+										"timestamp", "timestamptz"),
+								 errhint("use *_tz() function for timezone support")));
+					val2 = timestamp2timestamptz_opt_error(val2, have_error);
+					if (have_error && *have_error)
+						return 0;
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*have_error = true;
+					return 0;
+			}
+			break;
+
+		default:
+			elog(ERROR, "unrecognized SQL/JSON datetime type oid: %d",
+				 typid1);
+	}
+
+	if (*have_error)
+		return 0;
+
+	if (!cmpfunc)
+		elog(ERROR, "unrecognized SQL/JSON datetime type oid: %d",
+			 typid2);
+
+	*have_error = false;
+
+	return DatumGetInt32(DirectFunctionCall2(cmpfunc, val1, val2));
+}
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
index 1725502ede2..252f7051f65 100644
--- a/src/backend/utils/adt/jsonpath_gram.y
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -94,12 +94,14 @@ static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr,
 %token	<str>		LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
 %token	<str>		ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P
 %token	<str>		ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P KEYVALUE_P
+%token	<str>		DATETIME_P
 
 %type	<result>	result
 
 %type	<value>		scalar_value path_primary expr array_accessor
 					any_path accessor_op key predicate delimited_predicate
 					index_elem starts_with_initial expr_or_predicate
+					datetime_template opt_datetime_template
 
 %type	<elems>		accessor_expr
 
@@ -247,9 +249,20 @@ accessor_op:
 	| array_accessor				{ $$ = $1; }
 	| '.' any_path					{ $$ = $2; }
 	| '.' method '(' ')'			{ $$ = makeItemType($2); }
+	| '.' DATETIME_P '(' opt_datetime_template ')'
+									{ $$ = makeItemUnary(jpiDatetime, $4); }
 	| '?' '(' predicate ')'			{ $$ = makeItemUnary(jpiFilter, $3); }
 	;
 
+datetime_template:
+	STRING_P						{ $$ = makeItemString(&$1); }
+	;
+
+opt_datetime_template:
+	datetime_template				{ $$ = $1; }
+	| /* EMPTY */					{ $$ = NULL; }
+	;
+
 key:
 	key_name						{ $$ = makeItemKey(&$1); }
 	;
@@ -272,6 +285,7 @@ key_name:
 	| FLOOR_P
 	| DOUBLE_P
 	| CEILING_P
+	| DATETIME_P
 	| KEYVALUE_P
 	| LAST_P
 	| STARTS_P
diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l
index e35636883a1..9650226f507 100644
--- a/src/backend/utils/adt/jsonpath_scan.l
+++ b/src/backend/utils/adt/jsonpath_scan.l
@@ -323,6 +323,7 @@ static const JsonPathKeyword keywords[] = {
 	{ 6, false,	STRICT_P,	"strict"},
 	{ 7, false,	CEILING_P,	"ceiling"},
 	{ 7, false,	UNKNOWN_P,	"unknown"},
+	{ 8, false,	DATETIME_P,	"datetime"},
 	{ 8, false,	KEYVALUE_P,	"keyvalue"},
 	{ 10,false, LIKE_REGEX_P, "like_regex"},
 };
diff --git a/src/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index 867e98b690b..2bd283085e3 100644
--- a/src/backend/utils/errcodes.txt
+++ b/src/backend/utils/errcodes.txt
@@ -207,6 +207,7 @@ Section: Class 22 - Data Exception
 2200S    E    ERRCODE_INVALID_XML_COMMENT                                    invalid_xml_comment
 2200T    E    ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION                     invalid_xml_processing_instruction
 22030    E    ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE                        duplicate_json_object_key_value
+22031    E    ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION            invalid_argument_for_json_datetime_function
 22032    E    ERRCODE_INVALID_JSON_TEXT                                      invalid_json_text
 22033    E    ERRCODE_INVALID_SQL_JSON_SUBSCRIPT                             invalid_sql_json_subscript
 22034    E    ERRCODE_MORE_THAN_ONE_SQL_JSON_ITEM                            more_than_one_sql_json_item
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 5e69deeb7ae..f9c55d25a77 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9332,6 +9332,28 @@
   proname => 'jsonb_path_match', prorettype => 'bool',
   proargtypes => 'jsonb jsonpath jsonb bool', prosrc => 'jsonb_path_match' },
 
+{ oid => '6015', descr => 'jsonpath exists test with timezone',
+  proname => 'jsonb_path_exists_tz', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_exists_tz' },
+{ oid => '6016', descr => 'jsonpath query with timezone',
+  proname => 'jsonb_path_query_tz', provolatile => 's',
+  prorows => '1000', proretset => 't',
+  prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_query_tz' },
+{ oid => '6017', descr => 'jsonpath query wrapped into array with timezone',
+  proname => 'jsonb_path_query_array_tz', provolatile => 's',
+  prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_query_array_tz' },
+{ oid => '6018', descr => 'jsonpath query first item with timezone',
+  proname => 'jsonb_path_query_first_tz', provolatile => 's',
+  prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_query_first_tz' },
+{ oid => '6019', descr => 'jsonpath match with timezone',
+  proname => 'jsonb_path_match_tz', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'jsonb jsonpath jsonb bool',
+  prosrc => 'jsonb_path_match_tz' },
+
 { oid => '4010', descr => 'implementation of @? operator',
   proname => 'jsonb_path_exists_opr', prorettype => 'bool',
   proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_path_exists_opr' },
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 8458346bd4c..0f2b08efe6e 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -79,6 +79,7 @@ typedef enum JsonPathItemType
 	jpiFloor,					/* .floor() item method */
 	jpiCeiling,					/* .ceiling() item method */
 	jpiDouble,					/* .double() item method */
+	jpiDatetime,				/* .datetime() item method */
 	jpiKeyValue,				/* .keyvalue() item method */
 	jpiSubscript,				/* array subscript: 'expr' or 'expr TO expr' */
 	jpiLast,					/* LAST array subscript */
diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
index d9618f2d887..00124bd4f13 100644
--- a/src/test/regress/expected/jsonb_jsonpath.out
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -1659,6 +1659,532 @@ select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ lik
  "a\b"
 (1 row)
 
+select jsonb_path_query('null', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('true', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('1', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('[]', '$.datetime()');
+ jsonb_path_query 
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'strict $.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('{}', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  jsonpath item method .datetime() can only be applied to a string
+select jsonb_path_query('""', '$.datetime()');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  unrecognized datetime format
+HINT:  use datetime template argument for explicit format specification
+select jsonb_path_query('"12:34"', '$.datetime("aaa")');
+ERROR:  invalid datetime format separator: "a"
+select jsonb_path_query('"aaaa"', '$.datetime("HH24")');
+ERROR:  invalid value "aa" for "HH24"
+DETAIL:  Value must be an integer.
+select jsonb '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")';
+ ?column? 
+----------
+ t
+(1 row)
+
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+ jsonb_path_query 
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+ jsonb_path_query 
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+ERROR:  trailing characters remain in input string after datetime format
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+ERROR:  trailing characters remain in input string after datetime format
+select jsonb_path_query('"10-03-2017 12:34"', '       $.datetime("dd-mm-yyyy HH24:MI").type()');
+       jsonb_path_query        
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+      jsonb_path_query      
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+     jsonb_path_query     
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+   jsonb_path_query    
+-----------------------
+ "time with time zone"
+(1 row)
+
+set time zone '+00';
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+ jsonb_path_query 
+------------------
+ "12:34:00"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone '+10';
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+ jsonb_path_query 
+------------------
+ "12:34:00"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR:  input string is too short for datetime format
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query 
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone default;
+select jsonb_path_query('"2017-03-10"', '$.datetime().type()');
+ jsonb_path_query 
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"2017-03-10"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+       jsonb_path_query        
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:34:56"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()');
+      jsonb_path_query      
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:56+03:00"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()');
+      jsonb_path_query      
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:56+03:10"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime().type()');
+     jsonb_path_query     
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "12:34:56"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()');
+   jsonb_path_query    
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "12:34:56+03:00"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()');
+   jsonb_path_query    
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()');
+ jsonb_path_query 
+------------------
+ "12:34:56+03:10"
+(1 row)
+
+set time zone '+00';
+-- date comparison
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR:  cannot convert value from date to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR:  cannot convert value from date to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+ERROR:  cannot convert value from date to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10"
+ "2017-03-10T00:00:00"
+ "2017-03-10T03:00:00+03:00"
+(3 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10"
+ "2017-03-11"
+ "2017-03-10T00:00:00"
+ "2017-03-10T12:34:56"
+ "2017-03-10T03:00:00+03:00"
+(5 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-09"
+ "2017-03-10T01:02:03+04:00"
+(2 rows)
+
+-- time comparison
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00"
+ "12:35:00+00:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00"
+ "12:36:00"
+ "12:35:00+00:00"
+(3 rows)
+
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:34:00"
+ "12:35:00+01:00"
+ "13:35:00+01:00"
+(3 rows)
+
+-- timetz comparison
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+ERROR:  cannot convert value from time to timetz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00+01:00"
+(1 row)
+
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:35:00+01:00"
+ "12:36:00+01:00"
+ "12:35:00-02:00"
+ "11:35:00"
+ "12:35:00"
+(5 rows)
+
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query_tz 
+---------------------
+ "12:34:00+01:00"
+ "12:35:00+02:00"
+ "10:35:00"
+(3 rows)
+
+-- timestamp comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T13:35:00+01:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T12:36:00"
+ "2017-03-10T13:35:00+01:00"
+ "2017-03-10T12:35:00-01:00"
+ "2017-03-11"
+(5 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:34:00"
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10"
+(3 rows)
+
+-- timestamptz comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
+HINT:  use *_tz() function for timezone support
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T11:35:00"
+(2 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+     jsonb_path_query_tz     
+-----------------------------
+ "2017-03-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+set time zone default;
 -- jsonpath operators
 SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
  jsonb_path_query 
diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
index fc971dc408e..e399fa96312 100644
--- a/src/test/regress/expected/jsonpath.out
+++ b/src/test/regress/expected/jsonpath.out
@@ -395,6 +395,18 @@ select '$.keyvalue().key'::jsonpath;
  $.keyvalue()."key"
 (1 row)
 
+select '$.datetime()'::jsonpath;
+   jsonpath   
+--------------
+ $.datetime()
+(1 row)
+
+select '$.datetime("datetime template")'::jsonpath;
+            jsonpath             
+---------------------------------
+ $.datetime("datetime template")
+(1 row)
+
 select '$ ? (@ starts with "abc")'::jsonpath;
         jsonpath         
 -------------------------
diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
index ae8549d5536..246e38b9edd 100644
--- a/src/test/regress/sql/jsonb_jsonpath.sql
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -346,6 +346,178 @@ select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ lik
 select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\B$" flag "iq")');
 select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\b$" flag "")');
 
+select jsonb_path_query('null', '$.datetime()');
+select jsonb_path_query('true', '$.datetime()');
+select jsonb_path_query('1', '$.datetime()');
+select jsonb_path_query('[]', '$.datetime()');
+select jsonb_path_query('[]', 'strict $.datetime()');
+select jsonb_path_query('{}', '$.datetime()');
+select jsonb_path_query('""', '$.datetime()');
+select jsonb_path_query('"12:34"', '$.datetime("aaa")');
+select jsonb_path_query('"aaaa"', '$.datetime("HH24")');
+
+select jsonb '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")';
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+
+select jsonb_path_query('"10-03-2017 12:34"', '       $.datetime("dd-mm-yyyy HH24:MI").type()');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+
+set time zone '+00';
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone '+10';
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone default;
+
+select jsonb_path_query('"2017-03-10"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()');
+select jsonb_path_query('"12:34:56"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56"', '$.datetime()');
+select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56 +3"', '$.datetime()');
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()');
+
+set time zone '+00';
+
+-- date comparison
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query_tz(
+	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
+
+-- time comparison
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+select jsonb_path_query(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+select jsonb_path_query_tz(
+	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
+
+-- timetz comparison
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
+
+-- timestamp comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+
+-- timestamptz comparison
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query_tz(
+	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+
+set time zone default;
+
 -- jsonpath operators
 
 SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
index 7afe2528c3b..17ab7757831 100644
--- a/src/test/regress/sql/jsonpath.sql
+++ b/src/test/regress/sql/jsonpath.sql
@@ -71,6 +71,8 @@ select '"aaa".type()'::jsonpath;
 select 'true.type()'::jsonpath;
 select '$.double().floor().ceiling().abs()'::jsonpath;
 select '$.keyvalue().key'::jsonpath;
+select '$.datetime()'::jsonpath;
+select '$.datetime("datetime template")'::jsonpath;
 
 select '$ ? (@ starts with "abc")'::jsonpath;
 select '$ ? (@ starts with $var)'::jsonpath;
-- 
2.14.3

0006-Introduce-RRRR-and-RR-revise-YYY-YY-and-Y-datetime-8.patchapplication/octet-stream; name=0006-Introduce-RRRR-and-RR-revise-YYY-YY-and-Y-datetime-8.patchDownload
From 408c830ba485c0f75d30993975c87dda061f417e Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Tue, 20 Aug 2019 07:57:11 +0300
Subject: [PATCH 6/6] Introduce RRRR and RR, revise YYY, YY and Y datetime
 format patterns

SQL Standard 2016 defines RR and RRRR format patterns and interprets YYY, YY
and Y format patterns in the different way than current implementation of
to_date()/to_timestamp() does.

According to standard YYY, YY and Y should get higher digits from current year.
Our current implementation gets higher digits so that the result is closest
to 2020.  Now, we get just higher digits from 2020 instead.  We use 2020 instead
of current year in order to keep our conversion immutable.

According to standard RR behavior is implementation-defined and should select
marching 4-digit year in the interval [CY - 100; CY + 100], where CY is current
year.  We use logic, which was previously used in YY, select the matching year
closest to 2020.

The change is made in favor of upcoming jsonpath .datetime() method.  Standard
applies to jsonpath .datetime() method and CAST (... FORMAT ...) SQL clause.
But in order to keep our behavior consistent, we apply this change to
to_date()/to_timestamp() too.

Discussion: https://postgr.es/m/CAPpHfdsZgYEra_PeCLGNoXOWYx6iU-S3wF8aX0ObQUcZU%2B4XTw%40mail.gmail.com
Author: Alexander Korotkov
---
 doc/src/sgml/func.sgml                 | 32 ++++++++++++++++---
 src/backend/utils/adt/formatting.c     | 57 +++++++++++++++++++++++++---------
 src/test/regress/expected/horology.out | 29 ++++++++++++-----
 src/test/regress/sql/horology.sql      | 12 +++++--
 4 files changed, 101 insertions(+), 29 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 6a1ee30c494..d8eae334836 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -6332,7 +6332,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
         <entry>year (4 or more digits) with comma</entry>
        </row>
        <row>
-        <entry><literal>YYYY</literal></entry>
+        <entry><literal>YYYY</literal>, <literal>RRRR</literal></entry>
         <entry>year (4 or more digits)</entry>
        </row>
        <row>
@@ -6363,6 +6363,10 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
         <entry><literal>I</literal></entry>
         <entry>last digit of ISO 8601 week-numbering year</entry>
        </row>
+       <row>
+        <entry><literal>RR</literal></entry>
+        <entry>last 2 digits of rounded year</entry>
+       </row>
        <row>
         <entry><literal>BC</literal>, <literal>bc</literal>,
         <literal>AD</literal> or <literal>ad</literal></entry>
@@ -6683,10 +6687,18 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
      <listitem>
       <para>
        In <function>to_timestamp</function> and <function>to_date</function>,
-       if the year format specification is less than four digits, e.g.
-       <literal>YYY</literal>, and the supplied year is less than four digits,
-       the year will be adjusted to be nearest to the year 2020, e.g.
-       <literal>95</literal> becomes 1995.
+       when using <literal>YYY</literal>, <literal>YY</literal> or
+       <literal>Y</literal> fields, then higher year digits are taken from
+       2020, e.g. <literal>7</literal> becomes 2027.
+      </para>
+     </listitem>
+
+     <listitem>
+      <para>
+       In <function>to_timestamp</function> and <function>to_date</function>,
+       when using <literal>RR</literal> field, the supplied year will be
+       adjusted to be nearest to the year 2020, e.g. <literal>95</literal>
+       becomes 1995.
       </para>
      </listitem>
 
@@ -6817,6 +6829,16 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
       </para>
      </listitem>
 
+     <listitem>
+      <para>
+        <function>to_char(interval)</function> formats <literal>HH</literal> and
+        <literal>HH12</literal> as shown on a 12-hour clock, for example zero hours
+        and 36 hours both output as <literal>12</literal>, while <literal>HH24</literal>
+        outputs the full hour value, which can exceed 23 in
+        an <type>interval</type> value.
+      </para>
+     </listitem>
+
     </itemizedlist>
    </para>
 
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index f7175df8da6..8998d8346c5 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -665,6 +665,8 @@ typedef enum
 	DCH_PM,
 	DCH_Q,
 	DCH_RM,
+	DCH_RRRR,
+	DCH_RR,
 	DCH_SSSSS,
 	DCH_SSSS,
 	DCH_SS,
@@ -718,6 +720,8 @@ typedef enum
 	DCH_pm,
 	DCH_q,
 	DCH_rm,
+	DCH_rrrr,
+	DCH_rr,
 	DCH_sssss,
 	DCH_ssss,
 	DCH_ss,
@@ -828,6 +832,8 @@ static const KeyWord DCH_keywords[] = {
 	{"PM", 2, DCH_PM, false, FROM_CHAR_DATE_NONE},
 	{"Q", 1, DCH_Q, true, FROM_CHAR_DATE_NONE}, /* Q */
 	{"RM", 2, DCH_RM, false, FROM_CHAR_DATE_GREGORIAN}, /* R */
+	{"RRRR", 4, DCH_RRRR, true, FROM_CHAR_DATE_GREGORIAN},
+	{"RR", 2, DCH_RR, true, FROM_CHAR_DATE_GREGORIAN},
 	{"SSSSS", 5, DCH_SSSS, true, FROM_CHAR_DATE_NONE},	/* S */
 	{"SSSS", 4, DCH_SSSS, true, FROM_CHAR_DATE_NONE},
 	{"SS", 2, DCH_SS, true, FROM_CHAR_DATE_NONE},
@@ -881,6 +887,8 @@ static const KeyWord DCH_keywords[] = {
 	{"pm", 2, DCH_pm, false, FROM_CHAR_DATE_NONE},
 	{"q", 1, DCH_Q, true, FROM_CHAR_DATE_NONE}, /* q */
 	{"rm", 2, DCH_rm, false, FROM_CHAR_DATE_GREGORIAN}, /* r */
+	{"rrrr", 4, DCH_RRRR, true, FROM_CHAR_DATE_GREGORIAN},
+	{"rr", 2, DCH_RR, true, FROM_CHAR_DATE_GREGORIAN},
 	{"sssss", 5, DCH_SSSS, true, FROM_CHAR_DATE_NONE},	/* s */
 	{"ssss", 4, DCH_SSSS, true, FROM_CHAR_DATE_NONE},
 	{"ss", 2, DCH_SS, true, FROM_CHAR_DATE_NONE},
@@ -1058,7 +1066,8 @@ static void dump_node(FormatNode *node, int max);
 
 static const char *get_th(char *num, int type);
 static char *str_numth(char *dest, char *num, int type);
-static int	adjust_partial_year_to_2020(int year);
+static int	adjust_partial_year_to_2020(int year, int ndigits);
+static int	adjust_partial_round_year_to_2020(int year);
 static int	strspace_len(char *str);
 static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode,
 							   bool *have_error);
@@ -2234,8 +2243,27 @@ is_next_separator(FormatNode *n)
 }
 
 
+/*
+ * Get lower 'ndigits' from 'year' while take other higher digits from
+ * 2020.
+ */
+static int
+adjust_partial_year_to_2020(int year, int ndigits)
+{
+	if (ndigits == 1)
+		return year + 2020;
+	else if (ndigits == 2 || ndigits == 3)
+		return year + 2000;
+	else
+		return year;
+}
+
+
+/*
+ * Get closest to 2020 4-digit year, which corresponds to given 2-digits year.
+ */
 static int
-adjust_partial_year_to_2020(int year)
+adjust_partial_round_year_to_2020(int year)
 {
 	/*
 	 * Adjust all dates toward 2020; this is effectively what happens when we
@@ -2247,12 +2275,6 @@ adjust_partial_year_to_2020(int year)
 	/* Force 70-99 into the 1900's */
 	else if (year < 100)
 		return year + 1900;
-	/* Force 100-519 into the 2000's */
-	else if (year < 520)
-		return year + 2000;
-	/* Force 520-999 into the 1000's */
-	else if (year < 1000)
-		return year + 1000;
 	else
 		return year;
 }
@@ -3065,6 +3087,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 					str_numth(s, s, S_TH_TYPE(n->suffix));
 				s += strlen(s);
 				break;
+			case DCH_RRRR:
 			case DCH_YYYY:
 			case DCH_IYYY:
 				sprintf(s, "%0*d",
@@ -3095,6 +3118,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 					str_numth(s, s, S_TH_TYPE(n->suffix));
 				s += strlen(s);
 				break;
+			case DCH_RR:
 			case DCH_YY:
 			case DCH_IY:
 				sprintf(s, "%0*d",
@@ -3536,6 +3560,7 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std,
 					SKIP_THth(s, n->suffix);
 				}
 				break;
+			case DCH_RRRR:
 			case DCH_YYYY:
 			case DCH_IYYY:
 				from_char_parse_int(&out->year, &s, n, have_error);
@@ -3547,17 +3572,22 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std,
 			case DCH_IYY:
 				len = from_char_parse_int(&out->year, &s, n, have_error);
 				CHECK_ERROR;
-				if (len < 4)
-					out->year = adjust_partial_year_to_2020(out->year);
+				out->year = adjust_partial_year_to_2020(out->year, len);
 				out->yysz = 3;
 				SKIP_THth(s, n->suffix);
 				break;
+			case DCH_RR:
+				from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
+				out->year = adjust_partial_round_year_to_2020(out->year);
+				out->yysz = 2;
+				SKIP_THth(s, n->suffix);
+				break;
 			case DCH_YY:
 			case DCH_IY:
 				len = from_char_parse_int(&out->year, &s, n, have_error);
 				CHECK_ERROR;
-				if (len < 4)
-					out->year = adjust_partial_year_to_2020(out->year);
+				out->year = adjust_partial_year_to_2020(out->year, len);
 				out->yysz = 2;
 				SKIP_THth(s, n->suffix);
 				break;
@@ -3565,8 +3595,7 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std,
 			case DCH_I:
 				len = from_char_parse_int(&out->year, &s, n, have_error);
 				CHECK_ERROR;
-				if (len < 4)
-					out->year = adjust_partial_year_to_2020(out->year);
+				out->year = adjust_partial_year_to_2020(out->year, len);
 				out->yysz = 1;
 				SKIP_THth(s, n->suffix);
 				break;
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index 6b53876e062..f68882cc173 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -2597,7 +2597,7 @@ SELECT to_timestamp('1,582nd VIII 21', 'Y,YYYth FMRM DD');
 (1 row)
 
 SELECT to_timestamp('15 "text between quote marks" 98 54 45',
-                    E'HH24 "\\"text between quote marks\\"" YY MI SS');
+                    E'HH24 "\\"text between quote marks\\"" RR MI SS');
          to_timestamp         
 ------------------------------
  Thu Jan 01 15:54:45 1998 PST
@@ -2618,19 +2618,22 @@ SELECT to_timestamp('2000January09Sunday', 'YYYYFMMonthDDFMDay');
 SELECT to_timestamp('97/Feb/16', 'YYMonDD');
 ERROR:  invalid value "/Fe" for "Mon"
 DETAIL:  The given value did not match any of the allowed values for this field.
+SELECT to_timestamp('97/Feb/16', 'RRMonDD');
+ERROR:  invalid value "/Fe" for "Mon"
+DETAIL:  The given value did not match any of the allowed values for this field.
 SELECT to_timestamp('97/Feb/16', 'YY:Mon:DD');
          to_timestamp         
 ------------------------------
- Sun Feb 16 00:00:00 1997 PST
+ Sat Feb 16 00:00:00 2097 PST
 (1 row)
 
-SELECT to_timestamp('97/Feb/16', 'FXYY:Mon:DD');
+SELECT to_timestamp('97/Feb/16', 'FXRR:Mon:DD');
          to_timestamp         
 ------------------------------
  Sun Feb 16 00:00:00 1997 PST
 (1 row)
 
-SELECT to_timestamp('97/Feb/16', 'FXYY/Mon/DD');
+SELECT to_timestamp('97/Feb/16', 'FXRR/Mon/DD');
          to_timestamp         
 ------------------------------
  Sun Feb 16 00:00:00 1997 PST
@@ -2642,6 +2645,12 @@ SELECT to_timestamp('19971116', 'YYYYMMDD');
  Sun Nov 16 00:00:00 1997 PST
 (1 row)
 
+SELECT to_timestamp('19971116', 'RRRRMMDD');
+         to_timestamp         
+------------------------------
+ Sun Nov 16 00:00:00 1997 PST
+(1 row)
+
 SELECT to_timestamp('20000-1116', 'YYYY-MMDD');
          to_timestamp          
 -------------------------------
@@ -2663,11 +2672,17 @@ SELECT to_timestamp('1997 BC 11 16', 'YYYY BC MM DD');
 SELECT to_timestamp('9-1116', 'Y-MMDD');
          to_timestamp         
 ------------------------------
- Mon Nov 16 00:00:00 2009 PST
+ Fri Nov 16 00:00:00 2029 PST
 (1 row)
 
 SELECT to_timestamp('95-1116', 'YY-MMDD');
          to_timestamp         
+------------------------------
+ Wed Nov 16 00:00:00 2095 PST
+(1 row)
+
+SELECT to_timestamp('95-1116', 'RR-MMDD');
+         to_timestamp         
 ------------------------------
  Thu Nov 16 00:00:00 1995 PST
 (1 row)
@@ -2675,7 +2690,7 @@ SELECT to_timestamp('95-1116', 'YY-MMDD');
 SELECT to_timestamp('995-1116', 'YYY-MMDD');
          to_timestamp         
 ------------------------------
- Thu Nov 16 00:00:00 1995 PST
+ Mon Nov 16 00:00:00 2995 PST
 (1 row)
 
 SELECT to_timestamp('2005426', 'YYYYWWD');
@@ -2711,7 +2726,7 @@ SELECT to_timestamp('05527', 'IYIWID');
 SELECT to_timestamp('5527', 'IIWID');
          to_timestamp         
 ------------------------------
- Sun Jan 01 00:00:00 2006 PST
+ Sun Dec 28 00:00:00 2025 PST
 (1 row)
 
 SELECT to_timestamp('2005364', 'IYYYIDDD');
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7a9da1e954..e35cb448508 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -344,7 +344,7 @@ SELECT to_timestamp('My birthday-> Year: 1976, Month: May, Day: 16',
 SELECT to_timestamp('1,582nd VIII 21', 'Y,YYYth FMRM DD');
 
 SELECT to_timestamp('15 "text between quote marks" 98 54 45',
-                    E'HH24 "\\"text between quote marks\\"" YY MI SS');
+                    E'HH24 "\\"text between quote marks\\"" RR MI SS');
 
 SELECT to_timestamp('05121445482000', 'MMDDHH24MISSYYYY');
 
@@ -352,14 +352,18 @@ SELECT to_timestamp('2000January09Sunday', 'YYYYFMMonthDDFMDay');
 
 SELECT to_timestamp('97/Feb/16', 'YYMonDD');
 
+SELECT to_timestamp('97/Feb/16', 'RRMonDD');
+
 SELECT to_timestamp('97/Feb/16', 'YY:Mon:DD');
 
-SELECT to_timestamp('97/Feb/16', 'FXYY:Mon:DD');
+SELECT to_timestamp('97/Feb/16', 'FXRR:Mon:DD');
 
-SELECT to_timestamp('97/Feb/16', 'FXYY/Mon/DD');
+SELECT to_timestamp('97/Feb/16', 'FXRR/Mon/DD');
 
 SELECT to_timestamp('19971116', 'YYYYMMDD');
 
+SELECT to_timestamp('19971116', 'RRRRMMDD');
+
 SELECT to_timestamp('20000-1116', 'YYYY-MMDD');
 
 SELECT to_timestamp('1997 AD 11 16', 'YYYY BC MM DD');
@@ -369,6 +373,8 @@ SELECT to_timestamp('9-1116', 'Y-MMDD');
 
 SELECT to_timestamp('95-1116', 'YY-MMDD');
 
+SELECT to_timestamp('95-1116', 'RR-MMDD');
+
 SELECT to_timestamp('995-1116', 'YYY-MMDD');
 
 SELECT to_timestamp('2005426', 'YYYYWWD');
-- 
2.14.3

0003-Error-suppression-support-for-upcoming-jsonpath-datetime-8.patchapplication/octet-stream; name=0003-Error-suppression-support-for-upcoming-jsonpath-datetime-8.patchDownload
From 707598cccfde09279336106883d76197a9f3bb4e Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Sun, 21 Jul 2019 01:14:48 +0300
Subject: [PATCH 3/6] Error suppression support for upcoming jsonpath
 .datetime() method

Add support of error suppression in some date and time manipulation functions
as it's required for jsonpath .datetime() method support.  This commit doesn't
use PG_TRY()/PG_CATCH() in order to implement that.  Instead, it provides
internal versions of date and time functions used, which support error
suppression.

Discussion: https://postgr.es/m/CAPpHfdsZgYEra_PeCLGNoXOWYx6iU-S3wF8aX0ObQUcZU%2B4XTw%40mail.gmail.com
Author: Alexander Korotkov, Nikita Glukhov
Reviewed-by: Anastasia Lubennikova, Peter Eisentraut
---
 src/backend/utils/adt/date.c       |  86 ++++--
 src/backend/utils/adt/formatting.c | 539 ++++++++++++++++++++++++-------------
 src/backend/utils/adt/timestamp.c  |  66 +++--
 src/include/utils/date.h           |   2 +
 src/include/utils/datetime.h       |   2 +
 src/include/utils/formatting.h     |   3 +-
 src/include/utils/timestamp.h      |   3 +
 7 files changed, 479 insertions(+), 222 deletions(-)

diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 9e291b5c7bc..fa50d79c05d 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -550,13 +550,15 @@ date_mii(PG_FUNCTION_ARGS)
 	PG_RETURN_DATEADT(result);
 }
 
+
 /*
- * Internal routines for promoting date to timestamp and timestamp with
- * time zone
+ * Promote date to timestamp.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero is returned.
  */
-
-static Timestamp
-date2timestamp(DateADT dateVal)
+Timestamp
+date2timestamp_opt_error(DateADT dateVal, bool *have_error)
 {
 	Timestamp	result;
 
@@ -572,9 +574,19 @@ date2timestamp(DateADT dateVal)
 		 * boundary need be checked for overflow.
 		 */
 		if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("date out of range for timestamp")));
+		{
+			if (have_error)
+			{
+				*have_error = true;
+				return (Timestamp) 0;
+			}
+			else
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("date out of range for timestamp")));
+			}
+		}
 
 		/* date is days since 2000, timestamp is microseconds since same... */
 		result = dateVal * USECS_PER_DAY;
@@ -583,8 +595,23 @@ date2timestamp(DateADT dateVal)
 	return result;
 }
 
+/*
+ * Single-argument version of date2timestamp_opt_error().
+ */
 static TimestampTz
-date2timestamptz(DateADT dateVal)
+date2timestamp(DateADT dateVal)
+{
+	return date2timestamp_opt_error(dateVal, NULL);
+}
+
+/*
+ * Promote date to timestamp with time zone.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero is returned.
+ */
+TimestampTz
+date2timestamptz_opt_error(DateADT dateVal, bool *have_error)
 {
 	TimestampTz result;
 	struct pg_tm tt,
@@ -603,9 +630,19 @@ date2timestamptz(DateADT dateVal)
 		 * boundary need be checked for overflow.
 		 */
 		if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("date out of range for timestamp")));
+		{
+			if (have_error)
+			{
+				*have_error = true;
+				return (TimestampTz) 0;
+			}
+			else
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("date out of range for timestamp")));
+			}
+		}
 
 		j2date(dateVal + POSTGRES_EPOCH_JDATE,
 			   &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday));
@@ -621,14 +658,33 @@ date2timestamptz(DateADT dateVal)
 		 * of time zone, check for allowed timestamp range after adding tz.
 		 */
 		if (!IS_VALID_TIMESTAMP(result))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("date out of range for timestamp")));
+		{
+			if (have_error)
+			{
+				*have_error = true;
+				return (TimestampTz) 0;
+			}
+			else
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						 errmsg("date out of range for timestamp")));
+			}
+		}
 	}
 
 	return result;
 }
 
+/*
+ * Single-argument version of date2timestamptz_opt_error().
+ */
+static TimestampTz
+date2timestamptz(DateADT dateVal)
+{
+	return date2timestamptz_opt_error(dateVal, NULL);
+}
+
 /*
  * date2timestamp_no_overflow
  *
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index bafeaf141ef..f7175df8da6 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -98,6 +98,43 @@
 #include "utils/numeric.h"
 #include "utils/pg_locale.h"
 
+/* ----------
+ * Convenience macros for error handling
+ * ----------
+ *
+ * Two macros below help to handle errors in functions that take
+ * 'bool *have_error' argument.  When this argument is not NULL, it's expected
+ * that function will suppress ereports when possible.  Instead it should
+ * return some default value and set *have_error flag.
+ *
+ * RETURN_ERROR() macro intended to wrap ereport() calls.  When have_error
+ * function argument is not NULL, then instead of ereport'ing we set
+ * *have_error flag and go to on_error label.  It's supposed that jump
+ * resources will be freed and some 'default' value returned.
+ *
+ * CHECK_ERROR() jumps on_error label when *have_error flag is defined and set.
+ * It's supposed to be used for immediate exit from the function on error
+ * after call of another function with 'bool *have_error' argument.
+ */
+#define RETURN_ERROR(throw_error) \
+do { \
+	if (have_error) \
+	{ \
+		*have_error = true; \
+		goto on_error; \
+	} \
+	else \
+	{ \
+		throw_error; \
+	} \
+} while (0)
+
+#define CHECK_ERROR \
+do { \
+	if (have_error && *have_error) \
+		goto on_error; \
+} while (0)
+
 /* ----------
  * Routines flags
  * ----------
@@ -1012,7 +1049,7 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 static void DCH_to_char(FormatNode *node, bool is_interval,
 						TmToChar *in, char *out, Oid collid);
 static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
-						  bool std);
+						  bool std, bool *have_error);
 
 #ifdef DEBUG_TO_FROM_CHAR
 static void dump_index(const KeyWord *k, const int *index);
@@ -1023,15 +1060,21 @@ static const char *get_th(char *num, int type);
 static char *str_numth(char *dest, char *num, int type);
 static int	adjust_partial_year_to_2020(int year);
 static int	strspace_len(char *str);
-static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode);
-static void from_char_set_int(int *dest, const int value, const FormatNode *node);
-static int	from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node);
-static int	from_char_parse_int(int *dest, char **src, FormatNode *node);
+static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode,
+							   bool *have_error);
+static void from_char_set_int(int *dest, const int value, const FormatNode *node,
+							  bool *have_error);
+static int	from_char_parse_int_len(int *dest, char **src, const int len,
+									FormatNode *node, bool *have_error);
+static int	from_char_parse_int(int *dest, char **src, FormatNode *node,
+								bool *have_error);
 static int	seq_search(char *name, const char *const *array, int type, int max, int *len);
-static int	from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
+static int	from_char_seq_search(int *dest, char **src,
+								 const char *const *array, int type, int max,
+								 FormatNode *node, bool *have_error);
 static void do_to_timestamp(text *date_txt, text *fmt, bool std,
 							struct pg_tm *tm, fsec_t *fsec, int *fprec,
-							uint32 *flags);
+							uint32 *flags, bool *have_error);
 static char *fill_str(char *str, int c, int max);
 static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
 static char *int_to_roman(int number);
@@ -2233,21 +2276,26 @@ strspace_len(char *str)
  *
  * Puke if the date mode has already been set, and the caller attempts to set
  * it to a conflicting mode.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
-from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode)
+from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode, bool *have_error)
 {
 	if (mode != FROM_CHAR_DATE_NONE)
 	{
 		if (tmfc->mode == FROM_CHAR_DATE_NONE)
 			tmfc->mode = mode;
 		else if (tmfc->mode != mode)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("invalid combination of date conventions"),
-					 errhint("Do not mix Gregorian and ISO week date "
-							 "conventions in a formatting template.")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("invalid combination of date conventions"),
+								  errhint("Do not mix Gregorian and ISO week date "
+										  "conventions in a formatting template."))));
 	}
+
+on_error:
+	return;
 }
 
 /*
@@ -2255,18 +2303,25 @@ from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode)
  *
  * Puke if the destination integer has previously been set to some other
  * non-zero value.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
-from_char_set_int(int *dest, const int value, const FormatNode *node)
+from_char_set_int(int *dest, const int value, const FormatNode *node,
+				  bool *have_error)
 {
 	if (*dest != 0 && *dest != value)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("conflicting values for \"%s\" field in formatting string",
-						node->key->name),
-				 errdetail("This value contradicts a previous setting for "
-						   "the same field type.")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("conflicting values for \"%s\" field in "
+									 "formatting string",
+									 node->key->name),
+							  errdetail("This value contradicts a previous setting "
+										"for the same field type."))));
 	*dest = value;
+
+on_error:
+	return;
 }
 
 /*
@@ -2288,9 +2343,13 @@ from_char_set_int(int *dest, const int value, const FormatNode *node)
  * Note that from_char_parse_int() provides a more convenient wrapper where
  * the length of the field is the same as the length of the format keyword (as
  * with DD and MI).
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and -1 is returned.
  */
 static int
-from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
+from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node,
+						bool *have_error)
 {
 	long		result;
 	char		copy[DCH_MAX_ITEM_SIZ + 1];
@@ -2323,51 +2382,60 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
 		char	   *last;
 
 		if (used < len)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("source string too short for \"%s\" formatting field",
-							node->key->name),
-					 errdetail("Field requires %d characters, but only %d "
-							   "remain.",
-							   len, used),
-					 errhint("If your source string is not fixed-width, try "
-							 "using the \"FM\" modifier.")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("source string too short for \"%s\" "
+										 "formatting field",
+										 node->key->name),
+								  errdetail("Field requires %d characters, "
+											"but only %d remain.",
+											len, used),
+								  errhint("If your source string is not fixed-width, "
+										  "try using the \"FM\" modifier."))));
 
 		errno = 0;
 		result = strtol(copy, &last, 10);
 		used = last - copy;
 
 		if (used > 0 && used < len)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("invalid value \"%s\" for \"%s\"",
-							copy, node->key->name),
-					 errdetail("Field requires %d characters, but only %d "
-							   "could be parsed.", len, used),
-					 errhint("If your source string is not fixed-width, try "
-							 "using the \"FM\" modifier.")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("invalid value \"%s\" for \"%s\"",
+										 copy, node->key->name),
+								  errdetail("Field requires %d characters, "
+											"but only %d could be parsed.",
+											len, used),
+								  errhint("If your source string is not fixed-width, "
+										  "try using the \"FM\" modifier."))));
 
 		*src += used;
 	}
 
 	if (*src == init)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("invalid value \"%s\" for \"%s\"",
-						copy, node->key->name),
-				 errdetail("Value must be an integer.")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("invalid value \"%s\" for \"%s\"",
+									 copy, node->key->name),
+							  errdetail("Value must be an integer."))));
 
 	if (errno == ERANGE || result < INT_MIN || result > INT_MAX)
-		ereport(ERROR,
-				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-				 errmsg("value for \"%s\" in source string is out of range",
-						node->key->name),
-				 errdetail("Value must be in the range %d to %d.",
-						   INT_MIN, INT_MAX)));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+							  errmsg("value for \"%s\" in source string is out of range",
+									 node->key->name),
+							  errdetail("Value must be in the range %d to %d.",
+										INT_MIN, INT_MAX))));
 
 	if (dest != NULL)
-		from_char_set_int(dest, (int) result, node);
+	{
+		from_char_set_int(dest, (int) result, node, have_error);
+		CHECK_ERROR;
+	}
+
 	return *src - init;
+
+on_error:
+	return -1;
 }
 
 /*
@@ -2380,9 +2448,9 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
  * required length explicitly.
  */
 static int
-from_char_parse_int(int *dest, char **src, FormatNode *node)
+from_char_parse_int(int *dest, char **src, FormatNode *node, bool *have_error)
 {
-	return from_char_parse_int_len(dest, src, node->key->len, node);
+	return from_char_parse_int_len(dest, src, node->key->len, node, have_error);
 }
 
 /* ----------
@@ -2465,11 +2533,12 @@ seq_search(char *name, const char *const *array, int type, int max, int *len)
  * pointed to by 'dest', advance 'src' to the end of the part of the string
  * which matched, and return the number of characters consumed.
  *
- * If the string doesn't match, throw an error.
+ * If the string doesn't match, throw an error if 'have_error' is NULL,
+ * otherwise set '*have_error' and return -1.
  */
 static int
-from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max,
-					 FormatNode *node)
+from_char_seq_search(int *dest, char **src, const char *const *array, int type,
+					 int max, FormatNode *node, bool *have_error)
 {
 	int			len;
 
@@ -2481,15 +2550,18 @@ from_char_seq_search(int *dest, char **src, const char *const *array, int type,
 		Assert(max <= DCH_MAX_ITEM_SIZ);
 		strlcpy(copy, *src, max + 1);
 
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("invalid value \"%s\" for \"%s\"",
-						copy, node->key->name),
-				 errdetail("The given value did not match any of the allowed "
-						   "values for this field.")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("invalid value \"%s\" for \"%s\"",
+									 copy, node->key->name),
+							  errdetail("The given value did not match any of "
+										"the allowed values for this field."))));
 	}
 	*src += len;
 	return len;
+
+on_error:
+	return -1;
 }
 
 /* ----------
@@ -3089,10 +3161,13 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
  *
  * Note: we currently don't have any to_interval() function, so there
  * is no need here for INVALID_FOR_INTERVAL checks.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  * ----------
  */
 static void
-DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
+DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std,
+			  bool *have_error)
 {
 	FormatNode *n;
 	char	   *s;
@@ -3132,10 +3207,10 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 				if (*s == n->character[0])
 					s++;
 				else
-					ereport(ERROR,
-							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-							 errmsg("unmatched format separator \"%c\"",
-									n->character[0])));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+										  errmsg("unmatched format separator \"%c\"",
+												 n->character[0]))));
 			}
 			else if (!fx_mode)
 			{
@@ -3191,7 +3266,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 			continue;
 		}
 
-		from_char_set_mode(out, n->key->date_mode);
+		from_char_set_mode(out, n->key->date_mode, have_error);
+		CHECK_ERROR;
 
 		switch (n->key->id)
 		{
@@ -3203,8 +3279,10 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 			case DCH_a_m:
 			case DCH_p_m:
 				from_char_seq_search(&value, &s, ampm_strings_long,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->pm, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->pm, value % 2, n, have_error);
+				CHECK_ERROR;
 				out->clock = CLOCK_12_HOUR;
 				break;
 			case DCH_AM:
@@ -3212,30 +3290,37 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 			case DCH_am:
 			case DCH_pm:
 				from_char_seq_search(&value, &s, ampm_strings,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->pm, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->pm, value % 2, n, have_error);
+				CHECK_ERROR;
 				out->clock = CLOCK_12_HOUR;
 				break;
 			case DCH_HH:
 			case DCH_HH12:
-				from_char_parse_int_len(&out->hh, &s, 2, n);
+				from_char_parse_int_len(&out->hh, &s, 2, n, have_error);
+				CHECK_ERROR;
 				out->clock = CLOCK_12_HOUR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_HH24:
-				from_char_parse_int_len(&out->hh, &s, 2, n);
+				from_char_parse_int_len(&out->hh, &s, 2, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_MI:
-				from_char_parse_int(&out->mi, &s, n);
+				from_char_parse_int(&out->mi, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_SS:
-				from_char_parse_int(&out->ss, &s, n);
+				from_char_parse_int(&out->ss, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_MS:		/* millisecond */
-				len = from_char_parse_int_len(&out->ms, &s, 3, n);
+				len = from_char_parse_int_len(&out->ms, &s, 3, n, have_error);
+				CHECK_ERROR;
 
 				/*
 				 * 25 is 0.25 and 250 is 0.25 too; 025 is 0.025 and not 0.25
@@ -3256,7 +3341,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 			case DCH_US:		/* microsecond */
 				len = from_char_parse_int_len(&out->us, &s,
 											  n->key->id == DCH_US ? 6 :
-											  out->ff, n);
+											  out->ff, n, have_error);
+				CHECK_ERROR;
 
 				out->us *= len == 1 ? 100000 :
 					len == 2 ? 10000 :
@@ -3267,16 +3353,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_SSSS:
-				from_char_parse_int(&out->ssss, &s, n);
+				from_char_parse_int(&out->ssss, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_tz:
 			case DCH_TZ:
 			case DCH_OF:
-				ereport(ERROR,
-						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						 errmsg("formatting field \"%s\" is only supported in to_char",
-								n->key->name)));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									  errmsg("formatting field \"%s\" is only supported in to_char",
+											 n->key->name))));
+				CHECK_ERROR;
 				break;
 			case DCH_TZH:
 
@@ -3300,82 +3388,102 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 						out->tzsign = +1;
 				}
 
-				from_char_parse_int_len(&out->tzh, &s, 2, n);
+				from_char_parse_int_len(&out->tzh, &s, 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_TZM:
 				/* assign positive timezone sign if TZH was not seen before */
 				if (!out->tzsign)
 					out->tzsign = +1;
-				from_char_parse_int_len(&out->tzm, &s, 2, n);
+				from_char_parse_int_len(&out->tzm, &s, 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_A_D:
 			case DCH_B_C:
 			case DCH_a_d:
 			case DCH_b_c:
 				from_char_seq_search(&value, &s, adbc_strings_long,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->bc, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->bc, value % 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_AD:
 			case DCH_BC:
 			case DCH_ad:
 			case DCH_bc:
 				from_char_seq_search(&value, &s, adbc_strings,
-									 ALL_UPPER, n->key->len, n);
-				from_char_set_int(&out->bc, value % 2, n);
+									 ALL_UPPER, n->key->len, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->bc, value % 2, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_MONTH:
 			case DCH_Month:
 			case DCH_month:
 				from_char_seq_search(&value, &s, months_full, ONE_UPPER,
-									 MAX_MONTH_LEN, n);
-				from_char_set_int(&out->mm, value + 1, n);
+									 MAX_MONTH_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, value + 1, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_MON:
 			case DCH_Mon:
 			case DCH_mon:
 				from_char_seq_search(&value, &s, months, ONE_UPPER,
-									 MAX_MON_LEN, n);
-				from_char_set_int(&out->mm, value + 1, n);
+									 MAX_MON_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, value + 1, n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_MM:
-				from_char_parse_int(&out->mm, &s, n);
+				from_char_parse_int(&out->mm, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_DAY:
 			case DCH_Day:
 			case DCH_day:
 				from_char_seq_search(&value, &s, days, ONE_UPPER,
-									 MAX_DAY_LEN, n);
-				from_char_set_int(&out->d, value, n);
+									 MAX_DAY_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->d, value, n, have_error);
+				CHECK_ERROR;
 				out->d++;
 				break;
 			case DCH_DY:
 			case DCH_Dy:
 			case DCH_dy:
 				from_char_seq_search(&value, &s, days, ONE_UPPER,
-									 MAX_DY_LEN, n);
-				from_char_set_int(&out->d, value, n);
+									 MAX_DY_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->d, value, n, have_error);
+				CHECK_ERROR;
 				out->d++;
 				break;
 			case DCH_DDD:
-				from_char_parse_int(&out->ddd, &s, n);
+				from_char_parse_int(&out->ddd, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_IDDD:
-				from_char_parse_int_len(&out->ddd, &s, 3, n);
+				from_char_parse_int_len(&out->ddd, &s, 3, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_DD:
-				from_char_parse_int(&out->dd, &s, n);
+				from_char_parse_int(&out->dd, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_D:
-				from_char_parse_int(&out->d, &s, n);
+				from_char_parse_int(&out->d, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_ID:
-				from_char_parse_int_len(&out->d, &s, 1, n);
+				from_char_parse_int_len(&out->d, &s, 1, n, have_error);
+				CHECK_ERROR;
 				/* Shift numbering to match Gregorian where Sunday = 1 */
 				if (++out->d > 7)
 					out->d = 1;
@@ -3383,7 +3491,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 				break;
 			case DCH_WW:
 			case DCH_IW:
-				from_char_parse_int(&out->ww, &s, n);
+				from_char_parse_int(&out->ww, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Q:
@@ -3398,11 +3507,13 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 				 * We still parse the source string for an integer, but it
 				 * isn't stored anywhere in 'out'.
 				 */
-				from_char_parse_int((int *) NULL, &s, n);
+				from_char_parse_int((int *) NULL, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_CC:
-				from_char_parse_int(&out->cc, &s, n);
+				from_char_parse_int(&out->cc, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Y_YYY:
@@ -3414,11 +3525,12 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 
 					matched = sscanf(s, "%d,%03d%n", &millennia, &years, &nch);
 					if (matched < 2)
-						ereport(ERROR,
-								(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-								 errmsg("invalid input string for \"Y,YYY\"")));
+						RETURN_ERROR(ereport(ERROR,
+											 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+											  errmsg("invalid input string for \"Y,YYY\""))));
 					years += (millennia * 1000);
-					from_char_set_int(&out->year, years, n);
+					from_char_set_int(&out->year, years, n, have_error);
+					CHECK_ERROR;
 					out->yysz = 4;
 					s += nch;
 					SKIP_THth(s, n->suffix);
@@ -3426,47 +3538,62 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 				break;
 			case DCH_YYYY:
 			case DCH_IYYY:
-				from_char_parse_int(&out->year, &s, n);
+				from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
 				out->yysz = 4;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_YYY:
 			case DCH_IYY:
-				if (from_char_parse_int(&out->year, &s, n) < 4)
+				len = from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
+				if (len < 4)
 					out->year = adjust_partial_year_to_2020(out->year);
 				out->yysz = 3;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_YY:
 			case DCH_IY:
-				if (from_char_parse_int(&out->year, &s, n) < 4)
+				len = from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
+				if (len < 4)
 					out->year = adjust_partial_year_to_2020(out->year);
 				out->yysz = 2;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Y:
 			case DCH_I:
-				if (from_char_parse_int(&out->year, &s, n) < 4)
+				len = from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
+				if (len < 4)
 					out->year = adjust_partial_year_to_2020(out->year);
 				out->yysz = 1;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_RM:
 				from_char_seq_search(&value, &s, rm_months_upper,
-									 ALL_UPPER, MAX_RM_LEN, n);
-				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n);
+									 ALL_UPPER, MAX_RM_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value,
+								  n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_rm:
 				from_char_seq_search(&value, &s, rm_months_lower,
-									 ALL_LOWER, MAX_RM_LEN, n);
-				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n);
+									 ALL_LOWER, MAX_RM_LEN, n, have_error);
+				CHECK_ERROR;
+				from_char_set_int(&out->mm, MONTHS_PER_YEAR - value,
+								  n, have_error);
+				CHECK_ERROR;
 				break;
 			case DCH_W:
-				from_char_parse_int(&out->w, &s, n);
+				from_char_parse_int(&out->w, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_J:
-				from_char_parse_int(&out->j, &s, n);
+				from_char_parse_int(&out->j, &s, n, have_error);
+				CHECK_ERROR;
 				SKIP_THth(s, n->suffix);
 				break;
 		}
@@ -3490,19 +3617,22 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 	if (std)
 	{
 		if (n->type != NODE_TYPE_END)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("input string is too short for datetime format")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("input string is too short for datetime format"))));
 
 		while (*s != '\0' && isspace((unsigned char) *s))
 			s++;
 
 		if (*s != '\0')
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("trailing characters remain in input string after "
-							"datetime format")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("trailing characters remain in input string "
+										 "after datetime format"))));
 	}
+
+on_error:
+	return;
 }
 
 /*
@@ -3523,9 +3653,13 @@ DCH_prevent_counter_overflow(void)
 	}
 }
 
-/* Get mask of date/time/zone components present in format nodes. */
+/*
+ * Get mask of date/time/zone components present in format nodes.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
+ */
 static int
-DCH_datetime_type(FormatNode *node)
+DCH_datetime_type(FormatNode *node, bool *have_error)
 {
 	FormatNode *n;
 	int			flags = 0;
@@ -3566,10 +3700,10 @@ DCH_datetime_type(FormatNode *node)
 			case DCH_tz:
 			case DCH_TZ:
 			case DCH_OF:
-				ereport(ERROR,
-						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						 errmsg("formatting field \"%s\" is only supported in to_char",
-								n->key->name)));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									  errmsg("formatting field \"%s\" is only supported in to_char",
+											 n->key->name))));
 				flags |= DCH_ZONED;
 				break;
 			case DCH_TZH:
@@ -3608,12 +3742,10 @@ DCH_datetime_type(FormatNode *node)
 			case DCH_Y_YYY:
 			case DCH_YYYY:
 			case DCH_IYYY:
-			case DCH_RRRR:
 			case DCH_YYY:
 			case DCH_IYY:
 			case DCH_YY:
 			case DCH_IY:
-			case DCH_RR:
 			case DCH_Y:
 			case DCH_I:
 			case DCH_RM:
@@ -3625,6 +3757,7 @@ DCH_datetime_type(FormatNode *node)
 		}
 	}
 
+on_error:
 	return flags;
 }
 
@@ -3919,7 +4052,7 @@ to_timestamp(PG_FUNCTION_ARGS)
 	fsec_t		fsec;
 	int			fprec;
 
-	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL, NULL);
 
 	/* Use the specified time zone, if any. */
 	if (tm.tm_zone)
@@ -3958,7 +4091,7 @@ to_date(PG_FUNCTION_ARGS)
 	struct pg_tm tm;
 	fsec_t		fsec;
 
-	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL, NULL);
 
 	/* Prevent overflow in Julian-day routines */
 	if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3985,17 +4118,21 @@ to_date(PG_FUNCTION_ARGS)
  * the presence of date/time/zone components in the format string.
  *
  * When timezone component is present, the corresponding offset is set to '*tz'.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero value is returned.
  */
 Datum
 parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
-			   int32 *typmod, int *tz)
+			   int32 *typmod, int *tz, bool *have_error)
 {
 	struct pg_tm tm;
 	fsec_t		fsec;
 	int			fprec = 0;
 	uint32		flags;
 
-	do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags);
+	do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags, have_error);
+	CHECK_ERROR;
 
 	*typmod = fprec ? fprec : -1;	/* fractional part precision */
 
@@ -4023,15 +4160,15 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 					 */
 					Assert(!strict);
 
-					ereport(ERROR,
-							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-							 errmsg("missing time zone in input string for type timestamptz")));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+										  errmsg("missing time zone in input string for type timestamptz"))));
 				}
 
 				if (tm2timestamp(&tm, fsec, tz, &result) != 0)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("timestamptz out of range")));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("timestamptz out of range"))));
 
 				AdjustTimestampForTypmod(&result, *typmod);
 
@@ -4043,9 +4180,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 				Timestamp	result;
 
 				if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("timestamp out of range")));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("timestamp out of range"))));
 
 				AdjustTimestampForTypmod(&result, *typmod);
 
@@ -4057,9 +4194,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 		{
 			if (flags & DCH_ZONED)
 			{
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-						 errmsg("datetime format is zoned but not timed")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+									  errmsg("datetime format is zoned but not timed"))));
 			}
 			else
 			{
@@ -4067,20 +4204,20 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 
 				/* Prevent overflow in Julian-day routines */
 				if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("date out of range: \"%s\"",
-									text_to_cstring(date_txt))));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("date out of range: \"%s\"",
+												 text_to_cstring(date_txt)))));
 
 				result = 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(result))
-					ereport(ERROR,
-							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-							 errmsg("date out of range: \"%s\"",
-									text_to_cstring(date_txt))));
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+										  errmsg("date out of range: \"%s\"",
+												 text_to_cstring(date_txt)))));
 
 				*typid = DATEOID;
 				return DateADTGetDatum(result);
@@ -4098,7 +4235,7 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 				int			dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
 
 				if (dterr)
-					DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz");
+					RETURN_ERROR(DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz"));
 			}
 			else
 			{
@@ -4109,15 +4246,15 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 				 */
 				Assert(!strict);
 
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-						 errmsg("missing time zone in input string for type timetz")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+									  errmsg("missing time zone in input string for type timetz"))));
 			}
 
 			if (tm2timetz(&tm, fsec, *tz, result) != 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-						 errmsg("timetz out of range")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+									  errmsg("timetz out of range"))));
 
 			AdjustTimeForTypmod(&result->time, *typmod);
 
@@ -4129,9 +4266,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 			TimeADT		result;
 
 			if (tm2time(&tm, fsec, &result) != 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-						 errmsg("time out of range")));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+									  errmsg("time out of range"))));
 
 			AdjustTimeForTypmod(&result, *typmod);
 
@@ -4141,11 +4278,12 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 	}
 	else
 	{
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-				 errmsg("datetime format is not dated and not timed")));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							  errmsg("datetime format is not dated and not timed"))));
 	}
 
+on_error:
 	return (Datum) 0;
 }
 
@@ -4161,17 +4299,24 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
  *
  * The TmFromChar is then analysed and converted into the final results in
  * struct 'tm' and 'fsec'.
+ *
+ * Bit mask of date/time/zone components found in 'fmt' is returned in 'flags'.
+ *
+ * 'std' specifies standard parsing mode.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
 do_to_timestamp(text *date_txt, text *fmt, bool std,
 				struct pg_tm *tm, fsec_t *fsec, int *fprec,
-				uint32 *flags)
+				uint32 *flags, bool *have_error)
 {
-	FormatNode *format;
+	FormatNode *format = NULL;
 	TmFromChar	tmfc;
 	int			fmt_len;
 	char	   *date_str;
 	int			fmask;
+	bool		incache = false;
 
 	date_str = text_to_cstring(date_txt);
 
@@ -4185,7 +4330,6 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 	if (fmt_len)
 	{
 		char	   *fmt_str;
-		bool		incache;
 
 		fmt_str = text_to_cstring(fmt);
 
@@ -4195,8 +4339,6 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 			 * Allocate new memory if format picture is bigger than static
 			 * cache and do not use cache (call parser always)
 			 */
-			incache = false;
-
 			format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode));
 
 			parse_format(format, fmt_str, DCH_keywords, DCH_suff, DCH_index,
@@ -4218,15 +4360,21 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 		/* dump_index(DCH_keywords, DCH_index); */
 #endif
 
-		DCH_from_char(format, date_str, &tmfc, std);
+		DCH_from_char(format, date_str, &tmfc, std, have_error);
+		CHECK_ERROR;
 
 		pfree(fmt_str);
 
 		if (flags)
-			*flags = DCH_datetime_type(format);
+			*flags = DCH_datetime_type(format, have_error);
 
 		if (!incache)
+		{
 			pfree(format);
+			format = NULL;
+		}
+
+		CHECK_ERROR;
 	}
 
 	DEBUG_TMFC(&tmfc);
@@ -4255,11 +4403,13 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 	if (tmfc.clock == CLOCK_12_HOUR)
 	{
 		if (tm->tm_hour < 1 || tm->tm_hour > HOURS_PER_DAY / 2)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("hour \"%d\" is invalid for the 12-hour clock",
-							tm->tm_hour),
-					 errhint("Use the 24-hour clock, or give an hour between 1 and 12.")));
+		{
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("hour \"%d\" is invalid for the 12-hour clock",
+										 tm->tm_hour),
+								  errhint("Use the 24-hour clock, or give an hour between 1 and 12."))));
+		}
 
 		if (tmfc.pm && tm->tm_hour < HOURS_PER_DAY / 2)
 			tm->tm_hour += HOURS_PER_DAY / 2;
@@ -4363,9 +4513,11 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 		 */
 
 		if (!tm->tm_year && !tmfc.bc)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-					 errmsg("cannot calculate day of year without year information")));
+		{
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+								  errmsg("cannot calculate day of year without year information"))));
+		}
 
 		if (tmfc.mode == FROM_CHAR_DATE_ISOWEEK)
 		{
@@ -4422,7 +4574,7 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 			 * said DTERR_MD_FIELD_OVERFLOW, because we don't want to print an
 			 * irrelevant hint about datestyle.
 			 */
-			DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp");
+			RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp"));
 		}
 	}
 
@@ -4431,7 +4583,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 		tm->tm_min < 0 || tm->tm_min >= MINS_PER_HOUR ||
 		tm->tm_sec < 0 || tm->tm_sec >= SECS_PER_MINUTE ||
 		*fsec < INT64CONST(0) || *fsec >= USECS_PER_SEC)
-		DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp");
+	{
+		RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp"));
+	}
 
 	/* Save parsed time-zone into tm->tm_zone if it was specified */
 	if (tmfc.tzsign)
@@ -4440,7 +4594,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 
 		if (tmfc.tzh < 0 || tmfc.tzh > MAX_TZDISP_HOUR ||
 			tmfc.tzm < 0 || tmfc.tzm >= MINS_PER_HOUR)
-			DateTimeParseError(DTERR_TZDISP_OVERFLOW, date_str, "timestamp");
+		{
+			RETURN_ERROR(DateTimeParseError(DTERR_TZDISP_OVERFLOW, date_str, "timestamp"));
+		}
 
 		tz = psprintf("%c%02d:%02d",
 					  tmfc.tzsign > 0 ? '+' : '-', tmfc.tzh, tmfc.tzm);
@@ -4450,6 +4606,11 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 
 	DEBUG_TM(tm);
 
+on_error:
+
+	if (format && !incache)
+		pfree(format);
+
 	pfree(date_str);
 }
 
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 2931bd58c0d..84bc97d40c3 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -329,11 +329,11 @@ timestamp_scale(PG_FUNCTION_ARGS)
 }
 
 /*
- * AdjustTimestampForTypmod --- round off a timestamp to suit given typmod
+ * AdjustTimestampForTypmodError --- round off a timestamp to suit given typmod
  * Works for either timestamp or timestamptz.
  */
-void
-AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
+bool
+AdjustTimestampForTypmodError(Timestamp *time, int32 typmod, bool *error)
 {
 	static const int64 TimestampScales[MAX_TIMESTAMP_PRECISION + 1] = {
 		INT64CONST(1000000),
@@ -359,10 +359,18 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
 		&& (typmod != -1) && (typmod != MAX_TIMESTAMP_PRECISION))
 	{
 		if (typmod < 0 || typmod > MAX_TIMESTAMP_PRECISION)
+		{
+			if (error)
+			{
+				*error = true;
+				return false;
+			}
+
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("timestamp(%d) precision must be between %d and %d",
 							typmod, 0, MAX_TIMESTAMP_PRECISION)));
+		}
 
 		if (*time >= INT64CONST(0))
 		{
@@ -375,8 +383,15 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
 					  * TimestampScales[typmod]);
 		}
 	}
+
+	return true;
 }
 
+void
+AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
+{
+	(void) AdjustTimestampForTypmodError(time, typmod, NULL);
+}
 
 /* timestamptz_in()
  * Convert a string to internal form.
@@ -5172,8 +5187,15 @@ timestamp_timestamptz(PG_FUNCTION_ARGS)
 	PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
 }
 
-static TimestampTz
-timestamp2timestamptz(Timestamp timestamp)
+/*
+ * Convert timestamp to timestamp with time zone.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero is returned.
+ */
+
+TimestampTz
+timestamp2timestamptz_opt_error(Timestamp timestamp, bool *have_error)
 {
 	TimestampTz result;
 	struct pg_tm tt,
@@ -5182,23 +5204,33 @@ timestamp2timestamptz(Timestamp timestamp)
 	int			tz;
 
 	if (TIMESTAMP_NOT_FINITE(timestamp))
-		result = timestamp;
-	else
-	{
-		if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("timestamp out of range")));
+		return timestamp;
 
+	if (!timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL))
+	{
 		tz = DetermineTimeZoneOffset(tm, session_timezone);
 
-		if (tm2timestamp(tm, fsec, &tz, &result) != 0)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("timestamp out of range")));
+		if (!tm2timestamp(tm, fsec, &tz, &result))
+			return result;
 	}
 
-	return result;
+	if (have_error)
+		*have_error = true;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+				 errmsg("timestamp out of range")));
+
+	return 0;
+}
+
+/*
+ * Single-argument version of timestamp2timestamptz_opt_error().
+ */
+static TimestampTz
+timestamp2timestamptz(Timestamp timestamp)
+{
+	return timestamp2timestamptz_opt_error(timestamp, NULL);
 }
 
 /* timestamptz_timestamp()
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index bd15bfa5bb0..c29f13aaf04 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -70,6 +70,8 @@ typedef struct
 /* date.c */
 extern int32 anytime_typmod_check(bool istz, int32 typmod);
 extern double date2timestamp_no_overflow(DateADT dateVal);
+extern Timestamp date2timestamp_opt_error(DateADT dateVal, bool *have_error);
+extern TimestampTz date2timestamptz_opt_error(DateADT dateVal, bool *have_error);
 extern void EncodeSpecialDate(DateADT dt, char *str);
 extern DateADT GetSQLCurrentDate(void);
 extern TimeTzADT *GetSQLCurrentTime(int32 typmod);
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index 0cafdd26538..5ebf336071b 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -337,5 +337,7 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
 extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
 
 extern void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
+extern bool AdjustTimestampForTypmodError(Timestamp *time, int32 typmod,
+										  bool *error);
 
 #endif							/* DATETIME_H */
diff --git a/src/include/utils/formatting.h b/src/include/utils/formatting.h
index beeaf10c332..165ca78ba5c 100644
--- a/src/include/utils/formatting.h
+++ b/src/include/utils/formatting.h
@@ -27,6 +27,7 @@ extern char *asc_toupper(const char *buff, size_t nbytes);
 extern char *asc_initcap(const char *buff, size_t nbytes);
 
 extern Datum parse_datetime(text *date_txt, text *fmt, bool std,
-							Oid *typid, int32 *typmod, int *tz);
+							Oid *typid, int32 *typmod, int *tz,
+							bool *have_error);
 
 #endif
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index ea16190ec3d..e884d4405f5 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -97,6 +97,9 @@ extern int	timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
 /* timestamp comparison works for timestamptz also */
 #define timestamptz_cmp_internal(dt1,dt2)	timestamp_cmp_internal(dt1, dt2)
 
+extern TimestampTz timestamp2timestamptz_opt_error(Timestamp timestamp,
+												   bool *have_error);
+
 extern int	isoweek2j(int year, int week);
 extern void isoweek2date(int woy, int *year, int *mon, int *mday);
 extern void isoweekdate2date(int isoweek, int wday, int *year, int *mon, int *mday);
-- 
2.14.3

0004-Allow-datetime-values-in-JsonbValue-8.patchapplication/octet-stream; name=0004-Allow-datetime-values-in-JsonbValue-8.patchDownload
From 1f9c3056f700ce16280e3236889b90cd04b8c231 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Sat, 20 Jul 2019 21:50:48 +0300
Subject: [PATCH 4/6] Allow datetime values in JsonbValue

SQL/JSON standard allows manipulation with datetime values.  So, it appears to
be convinient to allow datetime values to be represented in JsonbValue struct.
These datetime values are allowed for temporary representation only.  During
serialization datetime values are converted into strings.

SQL/JSON requires writing timestamps with timezone in the same timezone offset
as they were parsed.  This is why we allow storage of timezone offset in
JsonbValue struct.  For the same reason timezone offset argument is added to
JsonEncodeDateTime() function.

Extracted from original patch by Nikita Glukhov, Teodor Sigaev, Oleg Bartunov.
Revised by me.  Comments were adjusted by Liudmila Mantrova.

Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
Discussion: https://postgr.es/m/CAPpHfdsZgYEra_PeCLGNoXOWYx6iU-S3wF8aX0ObQUcZU%2B4XTw%40mail.gmail.com
Author: Nikita Glukhov, Teodor Sigaev, Oleg Bartunov, Alexander Korotkov, Liudmila Mantrova
Reviewed-by: Anastasia Lubennikova, Peter Eisentraut
---
 src/backend/utils/adt/json.c       | 32 ++++++++++++++++++++++++++------
 src/backend/utils/adt/jsonb.c      | 27 ++++++++++++++++++++++++---
 src/backend/utils/adt/jsonb_util.c | 21 +++++++++++++++++++++
 src/include/utils/jsonapi.h        |  3 ++-
 src/include/utils/jsonb.h          | 24 +++++++++++++++++++++---
 5 files changed, 94 insertions(+), 13 deletions(-)

diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index 26d293709aa..d4ba3bd87db 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -1506,7 +1506,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 			{
 				char		buf[MAXDATELEN + 1];
 
-				JsonEncodeDateTime(buf, val, DATEOID);
+				JsonEncodeDateTime(buf, val, DATEOID, NULL);
 				appendStringInfo(result, "\"%s\"", buf);
 			}
 			break;
@@ -1514,7 +1514,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 			{
 				char		buf[MAXDATELEN + 1];
 
-				JsonEncodeDateTime(buf, val, TIMESTAMPOID);
+				JsonEncodeDateTime(buf, val, TIMESTAMPOID, NULL);
 				appendStringInfo(result, "\"%s\"", buf);
 			}
 			break;
@@ -1522,7 +1522,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 			{
 				char		buf[MAXDATELEN + 1];
 
-				JsonEncodeDateTime(buf, val, TIMESTAMPTZOID);
+				JsonEncodeDateTime(buf, val, TIMESTAMPTZOID, NULL);
 				appendStringInfo(result, "\"%s\"", buf);
 			}
 			break;
@@ -1550,10 +1550,11 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 
 /*
  * Encode 'value' of datetime type 'typid' into JSON string in ISO format using
- * optionally preallocated buffer 'buf'.
+ * optionally preallocated buffer 'buf'.  Optional 'tzp' determines time-zone
+ * offset (in seconds) in which we want to show timestamptz.
  */
 char *
-JsonEncodeDateTime(char *buf, Datum value, Oid typid)
+JsonEncodeDateTime(char *buf, Datum value, Oid typid, const int *tzp)
 {
 	if (!buf)
 		buf = palloc(MAXDATELEN + 1);
@@ -1630,11 +1631,30 @@ JsonEncodeDateTime(char *buf, Datum value, Oid typid)
 				const char *tzn = NULL;
 
 				timestamp = DatumGetTimestampTz(value);
+
+				/*
+				 * If a time zone is specified, we apply the time-zone shift,
+				 * convert timestamptz to pg_tm as if it were without a time
+				 * zone, and then use the specified time zone for converting
+				 * the timestamp into a string.
+				 */
+				if (tzp)
+				{
+					tz = *tzp;
+					timestamp -= (TimestampTz) tz * USECS_PER_SEC;
+				}
+
 				/* Same as timestamptz_out(), but forcing DateStyle */
 				if (TIMESTAMP_NOT_FINITE(timestamp))
 					EncodeSpecialTimestamp(timestamp, buf);
-				else if (timestamp2tm(timestamp, &tz, &tm, &fsec, &tzn, NULL) == 0)
+				else if (timestamp2tm(timestamp, tzp ? NULL : &tz, &tm, &fsec,
+									  tzp ? NULL : &tzn, NULL) == 0)
+				{
+					if (tzp)
+						tm.tm_isdst = 1;	/* set time-zone presence flag */
+
 					EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf);
+				}
 				else
 					ereport(ERROR,
 							(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 69f41ab4556..74b4bbe44c6 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -206,6 +206,24 @@ JsonbTypeName(JsonbValue *jbv)
 			return "boolean";
 		case jbvNull:
 			return "null";
+		case jbvDatetime:
+			switch (jbv->val.datetime.typid)
+			{
+				case DATEOID:
+					return "date";
+				case TIMEOID:
+					return "time without time zone";
+				case TIMETZOID:
+					return "time with time zone";
+				case TIMESTAMPOID:
+					return "timestamp without time zone";
+				case TIMESTAMPTZOID:
+					return "timestamp with time zone";
+				default:
+					elog(ERROR, "unrecognized jsonb value datetime type: %d",
+						 jbv->val.datetime.typid);
+			}
+			return "unknown";
 		default:
 			elog(ERROR, "unrecognized jsonb value type: %d", jbv->type);
 			return "unknown";
@@ -805,17 +823,20 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result,
 				break;
 			case JSONBTYPE_DATE:
 				jb.type = jbvString;
-				jb.val.string.val = JsonEncodeDateTime(NULL, val, DATEOID);
+				jb.val.string.val = JsonEncodeDateTime(NULL, val,
+													   DATEOID, NULL);
 				jb.val.string.len = strlen(jb.val.string.val);
 				break;
 			case JSONBTYPE_TIMESTAMP:
 				jb.type = jbvString;
-				jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPOID);
+				jb.val.string.val = JsonEncodeDateTime(NULL, val,
+													   TIMESTAMPOID, NULL);
 				jb.val.string.len = strlen(jb.val.string.val);
 				break;
 			case JSONBTYPE_TIMESTAMPTZ:
 				jb.type = jbvString;
-				jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPTZOID);
+				jb.val.string.val = JsonEncodeDateTime(NULL, val,
+													   TIMESTAMPTZOID, NULL);
 				jb.val.string.len = strlen(jb.val.string.val);
 				break;
 			case JSONBTYPE_JSONCAST:
diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index 7e0d9de7f0c..f7f79eb965a 100644
--- a/src/backend/utils/adt/jsonb_util.c
+++ b/src/backend/utils/adt/jsonb_util.c
@@ -14,9 +14,12 @@
 #include "postgres.h"
 
 #include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
 #include "miscadmin.h"
 #include "utils/builtins.h"
+#include "utils/datetime.h"
 #include "utils/hashutils.h"
+#include "utils/jsonapi.h"
 #include "utils/jsonb.h"
 #include "utils/memutils.h"
 #include "utils/varlena.h"
@@ -244,6 +247,8 @@ compareJsonbContainers(JsonbContainer *a, JsonbContainer *b)
 						break;
 					case jbvBinary:
 						elog(ERROR, "unexpected jbvBinary value");
+					case jbvDatetime:
+						elog(ERROR, "unexpected jbvDatetime value");
 				}
 			}
 			else
@@ -1786,6 +1791,22 @@ convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal)
 				JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE;
 			break;
 
+		case jbvDatetime:
+			{
+				char		buf[MAXDATELEN + 1];
+				size_t		len;
+
+				JsonEncodeDateTime(buf,
+								   scalarVal->val.datetime.value,
+								   scalarVal->val.datetime.typid,
+								   &scalarVal->val.datetime.tz);
+				len = strlen(buf);
+				appendToBuffer(buffer, buf, len);
+
+				*jentry = len;
+			}
+			break;
+
 		default:
 			elog(ERROR, "invalid jsonb scalar type");
 	}
diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h
index 1c56acca55a..e1dab24d5dd 100644
--- a/src/include/utils/jsonapi.h
+++ b/src/include/utils/jsonapi.h
@@ -161,6 +161,7 @@ extern Jsonb *transform_jsonb_string_values(Jsonb *jsonb, void *action_state,
 extern text *transform_json_string_values(text *json, void *action_state,
 										  JsonTransformStringValuesAction transform_action);
 
-extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid);
+extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid,
+								const int *tzp);
 
 #endif							/* JSONAPI_H */
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index 35766e106a8..5e8179665e2 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -241,7 +241,15 @@ enum jbvType
 	jbvArray = 0x10,
 	jbvObject,
 	/* Binary (i.e. struct Jsonb) jbvArray/jbvObject */
-	jbvBinary
+	jbvBinary,
+
+	/*
+	 * Virtual types.
+	 *
+	 * These types are used only for in-memory JSON processing and serialized
+	 * into JSON strings when outputted to json/jsonb.
+	 */
+	jbvDatetime = 0x20,
 };
 
 /*
@@ -282,11 +290,21 @@ struct JsonbValue
 			int			len;
 			JsonbContainer *data;
 		}			binary;		/* Array or object, in on-disk format */
+
+		struct
+		{
+			Datum		value;
+			Oid			typid;
+			int32		typmod;
+			int			tz;		/* Numeric time zone, in seconds, for
+								 * TimestampTz data type */
+		}			datetime;
 	}			val;
 };
 
-#define IsAJsonbScalar(jsonbval)	((jsonbval)->type >= jbvNull && \
-									 (jsonbval)->type <= jbvBool)
+#define IsAJsonbScalar(jsonbval)	(((jsonbval)->type >= jbvNull && \
+									  (jsonbval)->type <= jbvBool) || \
+									  (jsonbval)->type == jbvDatetime)
 
 /*
  * Key/value pair within an Object.
-- 
2.14.3

0001-Implement-standard-datetime-parsing-mode-8.patchapplication/octet-stream; name=0001-Implement-standard-datetime-parsing-mode-8.patchDownload
From 2d2e27113cb1ec25240947bede69d7c0e244e1f0 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Mon, 26 Aug 2019 04:40:24 +0300
Subject: [PATCH 1/6] Implement standard datetime parsing mode

SQL Standard 2016 defines rules for handling separators in datetime template
strings, which are different to to_date()/to_timestamp() rules.  Stardard
allows only small set of separators and requires strict matching for them.

Standard applies to jsonpath .datetime() method and CAST (... FORMAT ...) SQL
clause.  We're not going to change handling of separators in existing
to_date()/to_timestamp() functions, because their current behavior is familiar
for users.  Standard behavior now available by special flag, which will be used
in upcoming .datetime() jsonpath method.

Discussion: https://postgr.es/m/CAPpHfdsZgYEra_PeCLGNoXOWYx6iU-S3wF8aX0ObQUcZU%2B4XTw%40mail.gmail.com
Author: Alexander Korotkov
---
 src/backend/utils/adt/formatting.c | 144 ++++++++++++++++++++++++++-----------
 1 file changed, 104 insertions(+), 40 deletions(-)

diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 053affa5cec..d2f7666eed6 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -99,11 +99,12 @@
 #include "utils/pg_locale.h"
 
 /* ----------
- * Routines type
+ * Routines flags
  * ----------
  */
-#define DCH_TYPE		1		/* DATE-TIME version	*/
-#define NUM_TYPE		2		/* NUMBER version	*/
+#define DCH_FLAG		0x1		/* DATE-TIME flag	*/
+#define NUM_FLAG		0x2		/* NUMBER flag	*/
+#define STD_FLAG		0x4		/* STANDARD flag	*/
 
 /* ----------
  * KeyWord Index (ascii from position 32 (' ') to 126 (~))
@@ -384,6 +385,7 @@ typedef struct
 {
 	FormatNode	format[DCH_CACHE_SIZE + 1];
 	char		str[DCH_CACHE_SIZE + 1];
+	bool		std;
 	bool		valid;
 	int			age;
 } DCHCacheEntry;
@@ -1000,11 +1002,12 @@ static const KeySuffix *suff_search(const char *str, const KeySuffix *suf, int t
 static bool is_separator_char(const char *str);
 static void NUMDesc_prepare(NUMDesc *num, FormatNode *n);
 static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
-						 const KeySuffix *suf, const int *index, int ver, NUMDesc *Num);
+						 const KeySuffix *suf, const int *index, uint32 flags, NUMDesc *Num);
 
 static void DCH_to_char(FormatNode *node, bool is_interval,
 						TmToChar *in, char *out, Oid collid);
-static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out);
+static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
+						  bool std);
 
 #ifdef DEBUG_TO_FROM_CHAR
 static void dump_index(const KeyWord *k, const int *index);
@@ -1021,7 +1024,7 @@ static int	from_char_parse_int_len(int *dest, char **src, const int len, FormatN
 static int	from_char_parse_int(int *dest, char **src, FormatNode *node);
 static int	seq_search(char *name, const char *const *array, int type, int max, int *len);
 static int	from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
-static void do_to_timestamp(text *date_txt, text *fmt,
+static void do_to_timestamp(text *date_txt, text *fmt, bool std,
 							struct pg_tm *tm, fsec_t *fsec, int *fprec);
 static char *fill_str(char *str, int c, int max);
 static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
@@ -1033,9 +1036,9 @@ static void NUM_numpart_to_char(NUMProc *Np, int id);
 static char *NUM_processor(FormatNode *node, NUMDesc *Num, char *inout,
 						   char *number, int input_len, int to_char_out_pre_spaces,
 						   int sign, bool is_to_char, Oid collid);
-static DCHCacheEntry *DCH_cache_getnew(const char *str);
-static DCHCacheEntry *DCH_cache_search(const char *str);
-static DCHCacheEntry *DCH_cache_fetch(const char *str);
+static DCHCacheEntry *DCH_cache_getnew(const char *str, bool std);
+static DCHCacheEntry *DCH_cache_search(const char *str, bool std);
+static DCHCacheEntry *DCH_cache_fetch(const char *str, bool std);
 static NUMCacheEntry *NUM_cache_getnew(const char *str);
 static NUMCacheEntry *NUM_cache_search(const char *str);
 static NUMCacheEntry *NUM_cache_fetch(const char *str);
@@ -1278,7 +1281,7 @@ NUMDesc_prepare(NUMDesc *num, FormatNode *n)
  */
 static void
 parse_format(FormatNode *node, const char *str, const KeyWord *kw,
-			 const KeySuffix *suf, const int *index, int ver, NUMDesc *Num)
+			 const KeySuffix *suf, const int *index, uint32 flags, NUMDesc *Num)
 {
 	FormatNode *n;
 
@@ -1296,7 +1299,7 @@ parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 		/*
 		 * Prefix
 		 */
-		if (ver == DCH_TYPE &&
+		if ((flags & DCH_FLAG) &&
 			(s = suff_search(str, suf, SUFFTYPE_PREFIX)) != NULL)
 		{
 			suffix |= s->id;
@@ -1317,13 +1320,13 @@ parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 			/*
 			 * NUM version: Prepare global NUMDesc struct
 			 */
-			if (ver == NUM_TYPE)
+			if (flags & NUM_FLAG)
 				NUMDesc_prepare(Num, n);
 
 			/*
 			 * Postfix
 			 */
-			if (ver == DCH_TYPE && *str &&
+			if ((flags & DCH_FLAG) && *str &&
 				(s = suff_search(str, suf, SUFFTYPE_POSTFIX)) != NULL)
 			{
 				n->suffix |= s->id;
@@ -1337,11 +1340,34 @@ parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 		{
 			int			chlen;
 
-			/*
-			 * Process double-quoted literal string, if any
-			 */
-			if (*str == '"')
+			if (flags & STD_FLAG)
+			{
+				/*
+				 * Standard mode, allow only following separators: "-./,':; "
+				 */
+				if (strchr("-./,':; ", *str) == NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							 errmsg("invalid datetime format separator: \"%s\"",
+									pnstrdup(str, pg_mblen(str)))));
+
+				if (*str == ' ')
+					n->type = NODE_TYPE_SPACE;
+				else
+					n->type = NODE_TYPE_SEPARATOR;
+
+				n->character[0] = *str;
+				n->character[1] = '\0';
+				n->key = NULL;
+				n->suffix = 0;
+				n++;
+				str++;
+			}
+			else if (*str == '"')
 			{
+				/*
+				 * Process double-quoted literal string, if any
+				 */
 				str++;
 				while (*str)
 				{
@@ -1373,7 +1399,7 @@ parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 					str++;
 				chlen = pg_mblen(str);
 
-				if (ver == DCH_TYPE && is_separator_char(str))
+				if ((flags & DCH_FLAG) && is_separator_char(str))
 					n->type = NODE_TYPE_SEPARATOR;
 				else if (isspace((unsigned char) *str))
 					n->type = NODE_TYPE_SPACE;
@@ -3060,13 +3086,13 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
  * ----------
  */
 static void
-DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
+DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 {
 	FormatNode *n;
 	char	   *s;
 	int			len,
 				value;
-	bool		fx_mode = false;
+	bool		fx_mode = std;
 
 	/* number of extra skipped characters (more than given in format string) */
 	int			extra_skip = 0;
@@ -3089,7 +3115,23 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 
 		if (n->type == NODE_TYPE_SPACE || n->type == NODE_TYPE_SEPARATOR)
 		{
-			if (!fx_mode)
+			if (std)
+			{
+				/*
+				 * Standard mode requires strict matching between format
+				 * string separators/spaces and input string.
+				 */
+				Assert(n->character[0] && !n->character[1]);
+
+				if (*s == n->character[0])
+					s++;
+				else
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							 errmsg("unmatched format separator \"%c\"",
+									n->character[0])));
+			}
+			else if (!fx_mode)
 			{
 				/*
 				 * In non FX (fixed format) mode one format string space or
@@ -3434,6 +3476,27 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 			}
 		}
 	}
+
+	/*
+	 * Standard parsing mode doesn't allow unmatched format patterns or
+	 * trailing characters in the input string.
+	 */
+	if (std)
+	{
+		if (n->type != NODE_TYPE_END)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+					 errmsg("input string is too short for datetime format")));
+
+		while (*s != '\0' && isspace((unsigned char) *s))
+			s++;
+
+		if (*s != '\0')
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+					 errmsg("trailing characters remain in input string after "
+							"datetime format")));
+	}
 }
 
 /*
@@ -3456,7 +3519,7 @@ DCH_prevent_counter_overflow(void)
 
 /* select a DCHCacheEntry to hold the given format picture */
 static DCHCacheEntry *
-DCH_cache_getnew(const char *str)
+DCH_cache_getnew(const char *str, bool std)
 {
 	DCHCacheEntry *ent;
 
@@ -3506,6 +3569,7 @@ DCH_cache_getnew(const char *str)
 			MemoryContextAllocZero(TopMemoryContext, sizeof(DCHCacheEntry));
 		ent->valid = false;
 		StrNCpy(ent->str, str, DCH_CACHE_SIZE + 1);
+		ent->std = std;
 		ent->age = (++DCHCounter);
 		/* caller is expected to fill format, then set valid */
 		++n_DCHCache;
@@ -3515,7 +3579,7 @@ DCH_cache_getnew(const char *str)
 
 /* look for an existing DCHCacheEntry matching the given format picture */
 static DCHCacheEntry *
-DCH_cache_search(const char *str)
+DCH_cache_search(const char *str, bool std)
 {
 	/* Ensure we can advance DCHCounter below */
 	DCH_prevent_counter_overflow();
@@ -3524,7 +3588,7 @@ DCH_cache_search(const char *str)
 	{
 		DCHCacheEntry *ent = DCHCache[i];
 
-		if (ent->valid && strcmp(ent->str, str) == 0)
+		if (ent->valid && strcmp(ent->str, str) == 0 && ent->std == std)
 		{
 			ent->age = (++DCHCounter);
 			return ent;
@@ -3536,21 +3600,21 @@ DCH_cache_search(const char *str)
 
 /* Find or create a DCHCacheEntry for the given format picture */
 static DCHCacheEntry *
-DCH_cache_fetch(const char *str)
+DCH_cache_fetch(const char *str, bool std)
 {
 	DCHCacheEntry *ent;
 
-	if ((ent = DCH_cache_search(str)) == NULL)
+	if ((ent = DCH_cache_search(str, std)) == NULL)
 	{
 		/*
 		 * Not in the cache, must run parser and save a new format-picture to
 		 * the cache.  Do not mark the cache entry valid until parsing
 		 * succeeds.
 		 */
-		ent = DCH_cache_getnew(str);
+		ent = DCH_cache_getnew(str, std);
 
-		parse_format(ent->format, str, DCH_keywords,
-					 DCH_suff, DCH_index, DCH_TYPE, NULL);
+		parse_format(ent->format, str, DCH_keywords, DCH_suff, DCH_index,
+					 DCH_FLAG | (std ? STD_FLAG : 0), NULL);
 
 		ent->valid = true;
 	}
@@ -3595,14 +3659,14 @@ datetime_to_char_body(TmToChar *tmtc, text *fmt, bool is_interval, Oid collid)
 		format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode));
 
 		parse_format(format, fmt_str, DCH_keywords,
-					 DCH_suff, DCH_index, DCH_TYPE, NULL);
+					 DCH_suff, DCH_index, DCH_FLAG, NULL);
 	}
 	else
 	{
 		/*
 		 * Use cache buffers
 		 */
-		DCHCacheEntry *ent = DCH_cache_fetch(fmt_str);
+		DCHCacheEntry *ent = DCH_cache_fetch(fmt_str, false);
 
 		incache = true;
 		format = ent->format;
@@ -3744,7 +3808,7 @@ to_timestamp(PG_FUNCTION_ARGS)
 	fsec_t		fsec;
 	int			fprec;
 
-	do_to_timestamp(date_txt, fmt, &tm, &fsec, &fprec);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec);
 
 	/* Use the specified time zone, if any. */
 	if (tm.tm_zone)
@@ -3783,7 +3847,7 @@ to_date(PG_FUNCTION_ARGS)
 	struct pg_tm tm;
 	fsec_t		fsec;
 
-	do_to_timestamp(date_txt, fmt, &tm, &fsec, NULL);
+	do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL);
 
 	/* Prevent overflow in Julian-day routines */
 	if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3818,7 +3882,7 @@ to_date(PG_FUNCTION_ARGS)
  * struct 'tm' and 'fsec'.
  */
 static void
-do_to_timestamp(text *date_txt, text *fmt,
+do_to_timestamp(text *date_txt, text *fmt, bool std,
 				struct pg_tm *tm, fsec_t *fsec, int *fprec)
 {
 	FormatNode *format;
@@ -3853,15 +3917,15 @@ do_to_timestamp(text *date_txt, text *fmt,
 
 			format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode));
 
-			parse_format(format, fmt_str, DCH_keywords,
-						 DCH_suff, DCH_index, DCH_TYPE, NULL);
+			parse_format(format, fmt_str, DCH_keywords, DCH_suff, DCH_index,
+						 DCH_FLAG | (std ? STD_FLAG : 0), NULL);
 		}
 		else
 		{
 			/*
 			 * Use cache buffers
 			 */
-			DCHCacheEntry *ent = DCH_cache_fetch(fmt_str);
+			DCHCacheEntry *ent = DCH_cache_fetch(fmt_str, std);
 
 			incache = true;
 			format = ent->format;
@@ -3872,7 +3936,7 @@ do_to_timestamp(text *date_txt, text *fmt,
 		/* dump_index(DCH_keywords, DCH_index); */
 #endif
 
-		DCH_from_char(format, date_str, &tmfc);
+		DCH_from_char(format, date_str, &tmfc, std);
 
 		pfree(fmt_str);
 
@@ -4241,7 +4305,7 @@ NUM_cache_fetch(const char *str)
 		zeroize_NUM(&ent->Num);
 
 		parse_format(ent->format, str, NUM_keywords,
-					 NULL, NUM_index, NUM_TYPE, &ent->Num);
+					 NULL, NUM_index, NUM_FLAG, &ent->Num);
 
 		ent->valid = true;
 	}
@@ -4273,7 +4337,7 @@ NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree)
 		zeroize_NUM(Num);
 
 		parse_format(format, str, NUM_keywords,
-					 NULL, NUM_index, NUM_TYPE, Num);
+					 NULL, NUM_index, NUM_FLAG, Num);
 	}
 	else
 	{
-- 
2.14.3

#21Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Alexander Korotkov (#20)
1 attachment(s)
Re: Support for jsonpath .datetime() method

On Mon, Sep 23, 2019 at 10:05 PM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

I've reordered the patchset. I moved the most debatable patch, which
introduces RRRR and RR and changes parsing of YYY, YY and Y to the
end. I think we have enough of time in this release cycle to decide
whether we want this.

Patches 0001-0005 looks quite mature for me. I'm going to push this
if no objections. After pushing them, I'm going to start discussion
related to RR, YY and friends in separate thread.

Pushed. Remaining patch is attached. I'm going to start the separate
thread with its detailed explanation.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0001-Introduce-RRRR-and-RR-revise-YYY-YY-and-Y-datetime-9.patchapplication/octet-stream; name=0001-Introduce-RRRR-and-RR-revise-YYY-YY-and-Y-datetime-9.patchDownload
commit 1befe402b03a0e6a00e284aee461c59ca3911ca8
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Tue Aug 20 07:57:11 2019 +0300

    Introduce RRRR and RR, revise YYY, YY and Y datetime format patterns
    
    SQL Standard 2016 defines RR and RRRR format patterns and interprets YYY, YY
    and Y format patterns in the different way than current implementation of
    to_date()/to_timestamp() does.
    
    According to standard YYY, YY and Y should get higher digits from current year.
    Our current implementation gets higher digits so that the result is closest
    to 2020.  Now, we get just higher digits from 2020 instead.  We use 2020 instead
    of current year in order to keep our conversion immutable.
    
    According to standard RR behavior is implementation-defined and should select
    marching 4-digit year in the interval [CY - 100; CY + 100], where CY is current
    year.  We use logic, which was previously used in YY, select the matching year
    closest to 2020.
    
    The change is made in favor of upcoming jsonpath .datetime() method.  Standard
    applies to jsonpath .datetime() method and CAST (... FORMAT ...) SQL clause.
    But in order to keep our behavior consistent, we apply this change to
    to_date()/to_timestamp() too.
    
    Discussion: https://postgr.es/m/CAPpHfdsZgYEra_PeCLGNoXOWYx6iU-S3wF8aX0ObQUcZU%2B4XTw%40mail.gmail.com
    Author: Alexander Korotkov

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 67f1a828a89..cf06b94298a 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -6332,7 +6332,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
         <entry>year (4 or more digits) with comma</entry>
        </row>
        <row>
-        <entry><literal>YYYY</literal></entry>
+        <entry><literal>YYYY</literal>, <literal>RRRR</literal></entry>
         <entry>year (4 or more digits)</entry>
        </row>
        <row>
@@ -6363,6 +6363,10 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
         <entry><literal>I</literal></entry>
         <entry>last digit of ISO 8601 week-numbering year</entry>
        </row>
+       <row>
+        <entry><literal>RR</literal></entry>
+        <entry>last 2 digits of rounded year</entry>
+       </row>
        <row>
         <entry><literal>BC</literal>, <literal>bc</literal>,
         <literal>AD</literal> or <literal>ad</literal></entry>
@@ -6683,10 +6687,18 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
      <listitem>
       <para>
        In <function>to_timestamp</function> and <function>to_date</function>,
-       if the year format specification is less than four digits, e.g.
-       <literal>YYY</literal>, and the supplied year is less than four digits,
-       the year will be adjusted to be nearest to the year 2020, e.g.
-       <literal>95</literal> becomes 1995.
+       when using <literal>YYY</literal>, <literal>YY</literal> or
+       <literal>Y</literal> fields, then higher year digits are taken from
+       2020, e.g. <literal>7</literal> becomes 2027.
+      </para>
+     </listitem>
+
+     <listitem>
+      <para>
+       In <function>to_timestamp</function> and <function>to_date</function>,
+       when using <literal>RR</literal> field, the supplied year will be
+       adjusted to be nearest to the year 2020, e.g. <literal>95</literal>
+       becomes 1995.
       </para>
      </listitem>
 
@@ -6817,6 +6829,16 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
       </para>
      </listitem>
 
+     <listitem>
+      <para>
+        <function>to_char(interval)</function> formats <literal>HH</literal> and
+        <literal>HH12</literal> as shown on a 12-hour clock, for example zero hours
+        and 36 hours both output as <literal>12</literal>, while <literal>HH24</literal>
+        outputs the full hour value, which can exceed 23 in
+        an <type>interval</type> value.
+      </para>
+     </listitem>
+
     </itemizedlist>
    </para>
 
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index f7175df8da6..f090bf9e71a 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -665,6 +665,8 @@ typedef enum
 	DCH_PM,
 	DCH_Q,
 	DCH_RM,
+	DCH_RRRR,
+	DCH_RR,
 	DCH_SSSSS,
 	DCH_SSSS,
 	DCH_SS,
@@ -718,6 +720,8 @@ typedef enum
 	DCH_pm,
 	DCH_q,
 	DCH_rm,
+	DCH_rrrr,
+	DCH_rr,
 	DCH_sssss,
 	DCH_ssss,
 	DCH_ss,
@@ -828,6 +832,8 @@ static const KeyWord DCH_keywords[] = {
 	{"PM", 2, DCH_PM, false, FROM_CHAR_DATE_NONE},
 	{"Q", 1, DCH_Q, true, FROM_CHAR_DATE_NONE}, /* Q */
 	{"RM", 2, DCH_RM, false, FROM_CHAR_DATE_GREGORIAN}, /* R */
+	{"RRRR", 4, DCH_RRRR, true, FROM_CHAR_DATE_GREGORIAN},
+	{"RR", 2, DCH_RR, true, FROM_CHAR_DATE_GREGORIAN},
 	{"SSSSS", 5, DCH_SSSS, true, FROM_CHAR_DATE_NONE},	/* S */
 	{"SSSS", 4, DCH_SSSS, true, FROM_CHAR_DATE_NONE},
 	{"SS", 2, DCH_SS, true, FROM_CHAR_DATE_NONE},
@@ -881,6 +887,8 @@ static const KeyWord DCH_keywords[] = {
 	{"pm", 2, DCH_pm, false, FROM_CHAR_DATE_NONE},
 	{"q", 1, DCH_Q, true, FROM_CHAR_DATE_NONE}, /* q */
 	{"rm", 2, DCH_rm, false, FROM_CHAR_DATE_GREGORIAN}, /* r */
+	{"rrrr", 4, DCH_RRRR, true, FROM_CHAR_DATE_GREGORIAN},
+	{"rr", 2, DCH_RR, true, FROM_CHAR_DATE_GREGORIAN},
 	{"sssss", 5, DCH_SSSS, true, FROM_CHAR_DATE_NONE},	/* s */
 	{"ssss", 4, DCH_SSSS, true, FROM_CHAR_DATE_NONE},
 	{"ss", 2, DCH_SS, true, FROM_CHAR_DATE_NONE},
@@ -1058,7 +1066,8 @@ static void dump_node(FormatNode *node, int max);
 
 static const char *get_th(char *num, int type);
 static char *str_numth(char *dest, char *num, int type);
-static int	adjust_partial_year_to_2020(int year);
+static int	adjust_partial_year_to_2020(int year, int ndigits);
+static int	adjust_partial_round_year_to_2020(int year);
 static int	strspace_len(char *str);
 static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode,
 							   bool *have_error);
@@ -2234,8 +2243,27 @@ is_next_separator(FormatNode *n)
 }
 
 
+/*
+ * Get lower 'ndigits' from 'year' while take other higher digits from
+ * 2020.
+ */
+static int
+adjust_partial_year_to_2020(int year, int ndigits)
+{
+	if (ndigits == 1)
+		return year + 2020;
+	else if (ndigits == 2 || ndigits == 3)
+		return year + 2000;
+	else
+		return year;
+}
+
+
+/*
+ * Get closest to 2020 4-digit year, which corresponds to given 2-digits year.
+ */
 static int
-adjust_partial_year_to_2020(int year)
+adjust_partial_round_year_to_2020(int year)
 {
 	/*
 	 * Adjust all dates toward 2020; this is effectively what happens when we
@@ -2247,12 +2275,6 @@ adjust_partial_year_to_2020(int year)
 	/* Force 70-99 into the 1900's */
 	else if (year < 100)
 		return year + 1900;
-	/* Force 100-519 into the 2000's */
-	else if (year < 520)
-		return year + 2000;
-	/* Force 520-999 into the 1000's */
-	else if (year < 1000)
-		return year + 1000;
 	else
 		return year;
 }
@@ -3065,6 +3087,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 					str_numth(s, s, S_TH_TYPE(n->suffix));
 				s += strlen(s);
 				break;
+			case DCH_RRRR:
 			case DCH_YYYY:
 			case DCH_IYYY:
 				sprintf(s, "%0*d",
@@ -3095,6 +3118,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 					str_numth(s, s, S_TH_TYPE(n->suffix));
 				s += strlen(s);
 				break;
+			case DCH_RR:
 			case DCH_YY:
 			case DCH_IY:
 				sprintf(s, "%0*d",
@@ -3536,6 +3560,7 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std,
 					SKIP_THth(s, n->suffix);
 				}
 				break;
+			case DCH_RRRR:
 			case DCH_YYYY:
 			case DCH_IYYY:
 				from_char_parse_int(&out->year, &s, n, have_error);
@@ -3547,17 +3572,22 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std,
 			case DCH_IYY:
 				len = from_char_parse_int(&out->year, &s, n, have_error);
 				CHECK_ERROR;
-				if (len < 4)
-					out->year = adjust_partial_year_to_2020(out->year);
+				out->year = adjust_partial_year_to_2020(out->year, len);
 				out->yysz = 3;
 				SKIP_THth(s, n->suffix);
 				break;
+			case DCH_RR:
+				from_char_parse_int(&out->year, &s, n, have_error);
+				CHECK_ERROR;
+				out->year = adjust_partial_round_year_to_2020(out->year);
+				out->yysz = 2;
+				SKIP_THth(s, n->suffix);
+				break;
 			case DCH_YY:
 			case DCH_IY:
 				len = from_char_parse_int(&out->year, &s, n, have_error);
 				CHECK_ERROR;
-				if (len < 4)
-					out->year = adjust_partial_year_to_2020(out->year);
+				out->year = adjust_partial_year_to_2020(out->year, len);
 				out->yysz = 2;
 				SKIP_THth(s, n->suffix);
 				break;
@@ -3565,8 +3595,7 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std,
 			case DCH_I:
 				len = from_char_parse_int(&out->year, &s, n, have_error);
 				CHECK_ERROR;
-				if (len < 4)
-					out->year = adjust_partial_year_to_2020(out->year);
+				out->year = adjust_partial_year_to_2020(out->year, len);
 				out->yysz = 1;
 				SKIP_THth(s, n->suffix);
 				break;
@@ -3742,10 +3771,12 @@ DCH_datetime_type(FormatNode *node, bool *have_error)
 			case DCH_Y_YYY:
 			case DCH_YYYY:
 			case DCH_IYYY:
+			case DCH_RRRR:
 			case DCH_YYY:
 			case DCH_IYY:
 			case DCH_YY:
 			case DCH_IY:
+			case DCH_RR:
 			case DCH_Y:
 			case DCH_I:
 			case DCH_RM:
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index 6b53876e062..f68882cc173 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -2597,7 +2597,7 @@ SELECT to_timestamp('1,582nd VIII 21', 'Y,YYYth FMRM DD');
 (1 row)
 
 SELECT to_timestamp('15 "text between quote marks" 98 54 45',
-                    E'HH24 "\\"text between quote marks\\"" YY MI SS');
+                    E'HH24 "\\"text between quote marks\\"" RR MI SS');
          to_timestamp         
 ------------------------------
  Thu Jan 01 15:54:45 1998 PST
@@ -2618,19 +2618,22 @@ SELECT to_timestamp('2000January09Sunday', 'YYYYFMMonthDDFMDay');
 SELECT to_timestamp('97/Feb/16', 'YYMonDD');
 ERROR:  invalid value "/Fe" for "Mon"
 DETAIL:  The given value did not match any of the allowed values for this field.
+SELECT to_timestamp('97/Feb/16', 'RRMonDD');
+ERROR:  invalid value "/Fe" for "Mon"
+DETAIL:  The given value did not match any of the allowed values for this field.
 SELECT to_timestamp('97/Feb/16', 'YY:Mon:DD');
          to_timestamp         
 ------------------------------
- Sun Feb 16 00:00:00 1997 PST
+ Sat Feb 16 00:00:00 2097 PST
 (1 row)
 
-SELECT to_timestamp('97/Feb/16', 'FXYY:Mon:DD');
+SELECT to_timestamp('97/Feb/16', 'FXRR:Mon:DD');
          to_timestamp         
 ------------------------------
  Sun Feb 16 00:00:00 1997 PST
 (1 row)
 
-SELECT to_timestamp('97/Feb/16', 'FXYY/Mon/DD');
+SELECT to_timestamp('97/Feb/16', 'FXRR/Mon/DD');
          to_timestamp         
 ------------------------------
  Sun Feb 16 00:00:00 1997 PST
@@ -2642,6 +2645,12 @@ SELECT to_timestamp('19971116', 'YYYYMMDD');
  Sun Nov 16 00:00:00 1997 PST
 (1 row)
 
+SELECT to_timestamp('19971116', 'RRRRMMDD');
+         to_timestamp         
+------------------------------
+ Sun Nov 16 00:00:00 1997 PST
+(1 row)
+
 SELECT to_timestamp('20000-1116', 'YYYY-MMDD');
          to_timestamp          
 -------------------------------
@@ -2663,11 +2672,17 @@ SELECT to_timestamp('1997 BC 11 16', 'YYYY BC MM DD');
 SELECT to_timestamp('9-1116', 'Y-MMDD');
          to_timestamp         
 ------------------------------
- Mon Nov 16 00:00:00 2009 PST
+ Fri Nov 16 00:00:00 2029 PST
 (1 row)
 
 SELECT to_timestamp('95-1116', 'YY-MMDD');
          to_timestamp         
+------------------------------
+ Wed Nov 16 00:00:00 2095 PST
+(1 row)
+
+SELECT to_timestamp('95-1116', 'RR-MMDD');
+         to_timestamp         
 ------------------------------
  Thu Nov 16 00:00:00 1995 PST
 (1 row)
@@ -2675,7 +2690,7 @@ SELECT to_timestamp('95-1116', 'YY-MMDD');
 SELECT to_timestamp('995-1116', 'YYY-MMDD');
          to_timestamp         
 ------------------------------
- Thu Nov 16 00:00:00 1995 PST
+ Mon Nov 16 00:00:00 2995 PST
 (1 row)
 
 SELECT to_timestamp('2005426', 'YYYYWWD');
@@ -2711,7 +2726,7 @@ SELECT to_timestamp('05527', 'IYIWID');
 SELECT to_timestamp('5527', 'IIWID');
          to_timestamp         
 ------------------------------
- Sun Jan 01 00:00:00 2006 PST
+ Sun Dec 28 00:00:00 2025 PST
 (1 row)
 
 SELECT to_timestamp('2005364', 'IYYYIDDD');
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7a9da1e954..e35cb448508 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -344,7 +344,7 @@ SELECT to_timestamp('My birthday-> Year: 1976, Month: May, Day: 16',
 SELECT to_timestamp('1,582nd VIII 21', 'Y,YYYth FMRM DD');
 
 SELECT to_timestamp('15 "text between quote marks" 98 54 45',
-                    E'HH24 "\\"text between quote marks\\"" YY MI SS');
+                    E'HH24 "\\"text between quote marks\\"" RR MI SS');
 
 SELECT to_timestamp('05121445482000', 'MMDDHH24MISSYYYY');
 
@@ -352,14 +352,18 @@ SELECT to_timestamp('2000January09Sunday', 'YYYYFMMonthDDFMDay');
 
 SELECT to_timestamp('97/Feb/16', 'YYMonDD');
 
+SELECT to_timestamp('97/Feb/16', 'RRMonDD');
+
 SELECT to_timestamp('97/Feb/16', 'YY:Mon:DD');
 
-SELECT to_timestamp('97/Feb/16', 'FXYY:Mon:DD');
+SELECT to_timestamp('97/Feb/16', 'FXRR:Mon:DD');
 
-SELECT to_timestamp('97/Feb/16', 'FXYY/Mon/DD');
+SELECT to_timestamp('97/Feb/16', 'FXRR/Mon/DD');
 
 SELECT to_timestamp('19971116', 'YYYYMMDD');
 
+SELECT to_timestamp('19971116', 'RRRRMMDD');
+
 SELECT to_timestamp('20000-1116', 'YYYY-MMDD');
 
 SELECT to_timestamp('1997 AD 11 16', 'YYYY BC MM DD');
@@ -369,6 +373,8 @@ SELECT to_timestamp('9-1116', 'Y-MMDD');
 
 SELECT to_timestamp('95-1116', 'YY-MMDD');
 
+SELECT to_timestamp('95-1116', 'RR-MMDD');
+
 SELECT to_timestamp('995-1116', 'YYY-MMDD');
 
 SELECT to_timestamp('2005426', 'YYYYWWD');
#22Nikita Glukhov
n.gluhov@postgrespro.ru
In reply to: Alexander Korotkov (#21)
1 attachment(s)
Re: Support for jsonpath .datetime() method

On 25.09.2019 22:55, Alexander Korotkov wrote:

On Mon, Sep 23, 2019 at 10:05 PM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

I've reordered the patchset. I moved the most debatable patch, which
introduces RRRR and RR and changes parsing of YYY, YY and Y to the
end. I think we have enough of time in this release cycle to decide
whether we want this.

Patches 0001-0005 looks quite mature for me. I'm going to push this
if no objections. After pushing them, I'm going to start discussion
related to RR, YY and friends in separate thread.

Pushed. Remaining patch is attached. I'm going to start the separate
thread with its detailed explanation.

Attached patch with refactoring of compareDatetime() according
to the complaints of Tom Lane in [1]/messages/by-id/32308.1569455803@sss.pgh.pa.us:
 * extracted four subroutines for type conversions
 * extracted subroutine for error reporting
 * added default cases to all switches
 * have_error flag is expected to be not-NULL always
 * fixed errhint() message style

[1]: /messages/by-id/32308.1569455803@sss.pgh.pa.us

--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0001-Refactor-jsonpath-s-compareDatime.patchtext/x-patch; name=0001-Refactor-jsonpath-s-compareDatime.patchDownload
From 81d8de2f1d0e0d4ec44729d3d2976b1e63834b14 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Thu, 26 Sep 2019 17:52:40 +0300
Subject: [PATCH] Refactor jsonpath's compareDatime()

---
 src/backend/utils/adt/jsonpath_exec.c        | 181 ++++++++++++++-------------
 src/test/regress/expected/jsonb_jsonpath.out |  30 ++---
 2 files changed, 110 insertions(+), 101 deletions(-)

diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index a35f718..7e540e3 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -2298,7 +2298,7 @@ compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2, bool useTz)
 			break;
 		case jbvDatetime:
 			{
-				bool		have_error = false;
+				bool		have_error;
 
 				cmp = compareDatetime(jb1->val.datetime.value,
 									  jb1->val.datetime.typid,
@@ -2571,15 +2571,72 @@ wrapItemsInArray(const JsonValueList *items)
 	return pushJsonbValue(&ps, WJB_END_ARRAY, NULL);
 }
 
+/* Check if the timezone required for casting from type1 to type2 is used */
+static void
+checkTimezoneIsUsedForCast(bool useTz, const char *type1, const char *type2)
+{
+	if (!useTz)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot convert value from %s to %s without timezone usage",
+						type1, type2),
+				 errhint("Use *_tz() function for timezone support.")));
+}
+
+/* Convert date datum to timestamp datum */
+static Datum
+castDateToTimestamp(Datum dt, bool *have_error)
+{
+	Timestamp	ts = date2timestamp_opt_error(DatumGetDateADT(dt), have_error);
+
+	return TimestampGetDatum(ts);
+}
+
+/* Convert date datum to timestamptz datum */
+static Datum
+castDateToTimestampTz(Datum date, bool useTz, bool *have_error)
+{
+	TimestampTz tstz;
+
+	checkTimezoneIsUsedForCast(useTz, "date", "timestamptz");
+	tstz = date2timestamptz_opt_error(DatumGetDateADT(date), have_error);
+
+	return TimestampTzGetDatum(tstz);
+}
+
+/* Convert time datum to timetz datum */
+static Datum
+castTimeToTimeTz(Datum time, bool useTz)
+{
+	checkTimezoneIsUsedForCast(useTz, "time", "timetz");
+
+	return DirectFunctionCall1(time_timetz, time);
+}
+
+/* Convert timestamp datum to timestamptz datum */
+static Datum
+castTimestampToTimestampTz(Timestamp ts, bool useTz, bool *have_error)
+{
+	TimestampTz tstz;
+
+	checkTimezoneIsUsedForCast(useTz, "timestamp", "timestamptz");
+	tstz = timestamp2timestamptz_opt_error(DatumGetTimestamp(ts), have_error);
+
+	return TimestampTzGetDatum(tstz);
+}
+
 /*
  * Cross-type comparison of two datetime SQL/JSON items.  If items are
- * uncomparable, 'error' flag is set.
+ * uncomparable or there is an error during casting, 'have_error' flag is set.
+ * If the cast requires timezone and it is not used, then hard error is thrown.
  */
 static int
 compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
 				bool useTz, bool *have_error)
 {
-	PGFunction cmpfunc = NULL;
+	PGFunction cmpfunc;
+
+	*have_error = false;
 
 	switch (typid1)
 	{
@@ -2588,35 +2645,26 @@ compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
 			{
 				case DATEOID:
 					cmpfunc = date_cmp;
-
 					break;
 
 				case TIMESTAMPOID:
-					val1 = TimestampGetDatum(date2timestamp_opt_error(DatumGetDateADT(val1), have_error));
-					if (have_error && *have_error)
-						return 0;
+					val1 = castDateToTimestamp(val1, have_error);
 					cmpfunc = timestamp_cmp;
-
 					break;
 
 				case TIMESTAMPTZOID:
-					if (!useTz)
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("cannot convert value from %s to %s without timezone usage",
-										"date", "timestamptz"),
-								 errhint("use *_tz() function for timezone support")));
-					val1 = TimestampTzGetDatum(date2timestamptz_opt_error(DatumGetDateADT(val1), have_error));
-					if (have_error && *have_error)
-						return 0;
+					val1 = castDateToTimestampTz(val1, useTz, have_error);
 					cmpfunc = timestamp_cmp;
-
 					break;
 
 				case TIMEOID:
 				case TIMETZOID:
-					*have_error = true;
+					*have_error = true;		/* uncomparable types */
 					return 0;
+
+				default:
+					elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u",
+						 typid2);
 			}
 			break;
 
@@ -2625,26 +2673,22 @@ compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
 			{
 				case TIMEOID:
 					cmpfunc = time_cmp;
-
 					break;
 
 				case TIMETZOID:
-					if (!useTz)
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("cannot convert value from %s to %s without timezone usage",
-										"time", "timetz"),
-								 errhint("use *_tz() function for timezone support")));
-					val1 = DirectFunctionCall1(time_timetz, val1);
+					val1 = castTimeToTimeTz(val1, useTz);
 					cmpfunc = timetz_cmp;
-
 					break;
 
 				case DATEOID:
 				case TIMESTAMPOID:
 				case TIMESTAMPTZOID:
-					*have_error = true;
+					*have_error = true;		/* uncomparable types */
 					return 0;
+
+				default:
+					elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u",
+						 typid2);
 			}
 			break;
 
@@ -2652,27 +2696,23 @@ compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
 			switch (typid2)
 			{
 				case TIMEOID:
-					if (!useTz)
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("cannot convert value from %s to %s without timezone usage",
-										"time", "timetz"),
-								 errhint("use *_tz() function for timezone support")));
-					val2 = DirectFunctionCall1(time_timetz, val2);
+					val2 = castTimeToTimeTz(val2, useTz);
 					cmpfunc = timetz_cmp;
-
 					break;
 
 				case TIMETZOID:
 					cmpfunc = timetz_cmp;
-
 					break;
 
 				case DATEOID:
 				case TIMESTAMPOID:
 				case TIMESTAMPTZOID:
-					*have_error = true;
+					*have_error = true;		/* uncomparable types */
 					return 0;
+
+				default:
+					elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u",
+						 typid2);
 			}
 			break;
 
@@ -2680,36 +2720,27 @@ compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
 			switch (typid2)
 			{
 				case DATEOID:
-					val2 = TimestampGetDatum(date2timestamp_opt_error(DatumGetDateADT(val2), have_error));
-					if (have_error && *have_error)
-						return 0;
+					val2 = castDateToTimestamp(val2, have_error);
 					cmpfunc = timestamp_cmp;
-
 					break;
 
 				case TIMESTAMPOID:
 					cmpfunc = timestamp_cmp;
-
 					break;
 
 				case TIMESTAMPTZOID:
-					if (!useTz)
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("cannot convert value from %s to %s without timezone usage",
-										"timestamp", "timestamptz"),
-								 errhint("use *_tz() function for timezone support")));
-					val1 = TimestampTzGetDatum(timestamp2timestamptz_opt_error(DatumGetTimestamp(val1), have_error));
-					if (have_error && *have_error)
-						return 0;
+					val1 = castTimestampToTimestampTz(val1, useTz, have_error);
 					cmpfunc = timestamp_cmp;
-
 					break;
 
 				case TIMEOID:
 				case TIMETZOID:
-					*have_error = true;
+					*have_error = true;		/* uncomparable types */
 					return 0;
+
+				default:
+					elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u",
+						 typid2);
 			}
 			break;
 
@@ -2717,58 +2748,36 @@ compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
 			switch (typid2)
 			{
 				case DATEOID:
-					if (!useTz)
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("cannot convert value from %s to %s without timezone usage",
-										"date", "timestamptz"),
-								 errhint("use *_tz() function for timezone support")));
-					val2 = TimestampTzGetDatum(date2timestamptz_opt_error(DatumGetDateADT(val2), have_error));
-					if (have_error && *have_error)
-						return 0;
+					val2 = castDateToTimestampTz(val2, useTz, have_error);
 					cmpfunc = timestamp_cmp;
-
 					break;
 
 				case TIMESTAMPOID:
-					if (!useTz)
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("cannot convert value from %s to %s without timezone usage",
-										"timestamp", "timestamptz"),
-								 errhint("use *_tz() function for timezone support")));
-					val2 = TimestampTzGetDatum(timestamp2timestamptz_opt_error(DatumGetTimestamp(val2), have_error));
-					if (have_error && *have_error)
-						return 0;
+					val2 = castTimestampToTimestampTz(val2, useTz, have_error);
 					cmpfunc = timestamp_cmp;
-
 					break;
 
 				case TIMESTAMPTZOID:
 					cmpfunc = timestamp_cmp;
-
 					break;
 
 				case TIMEOID:
 				case TIMETZOID:
-					*have_error = true;
+					*have_error = true;		/* uncomparable types */
 					return 0;
+
+				default:
+					elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u",
+						 typid2);
 			}
 			break;
 
 		default:
-			elog(ERROR, "unrecognized SQL/JSON datetime type oid: %d",
-				 typid1);
+			elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u", typid1);
 	}
 
 	if (*have_error)
-		return 0;
-
-	if (!cmpfunc)
-		elog(ERROR, "unrecognized SQL/JSON datetime type oid: %d",
-			 typid2);
-
-	*have_error = false;
+		return 0;		/* cast error */
 
 	return DatumGetInt32(DirectFunctionCall2(cmpfunc, val1, val2));
 }
diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
index 063f1c2..4df3433 100644
--- a/src/test/regress/expected/jsonb_jsonpath.out
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -1949,17 +1949,17 @@ select jsonb_path_query(
 	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
 	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
 ERROR:  cannot convert value from date to timestamptz without timezone usage
-HINT:  use *_tz() function for timezone support
+HINT:  Use *_tz() function for timezone support.
 select jsonb_path_query(
 	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
 	'$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
 ERROR:  cannot convert value from date to timestamptz without timezone usage
-HINT:  use *_tz() function for timezone support
+HINT:  Use *_tz() function for timezone support.
 select jsonb_path_query(
 	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
 	'$[*].datetime() ? (@ <  "10.03.2017".datetime("dd.mm.yyyy"))');
 ERROR:  cannot convert value from date to timestamptz without timezone usage
-HINT:  use *_tz() function for timezone support
+HINT:  Use *_tz() function for timezone support.
 select jsonb_path_query_tz(
 	'["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
 	'$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
@@ -1996,17 +1996,17 @@ select jsonb_path_query(
 	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
 	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
 ERROR:  cannot convert value from time to timetz without timezone usage
-HINT:  use *_tz() function for timezone support
+HINT:  Use *_tz() function for timezone support.
 select jsonb_path_query(
 	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
 	'$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
 ERROR:  cannot convert value from time to timetz without timezone usage
-HINT:  use *_tz() function for timezone support
+HINT:  Use *_tz() function for timezone support.
 select jsonb_path_query(
 	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
 	'$[*].datetime() ? (@ <  "12:35".datetime("HH24:MI"))');
 ERROR:  cannot convert value from time to timetz without timezone usage
-HINT:  use *_tz() function for timezone support
+HINT:  Use *_tz() function for timezone support.
 select jsonb_path_query_tz(
 	'["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
 	'$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
@@ -2041,17 +2041,17 @@ select jsonb_path_query(
 	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
 	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
 ERROR:  cannot convert value from time to timetz without timezone usage
-HINT:  use *_tz() function for timezone support
+HINT:  Use *_tz() function for timezone support.
 select jsonb_path_query(
 	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
 	'$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
 ERROR:  cannot convert value from time to timetz without timezone usage
-HINT:  use *_tz() function for timezone support
+HINT:  Use *_tz() function for timezone support.
 select jsonb_path_query(
 	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
 	'$[*].datetime() ? (@ <  "12:35 +1".datetime("HH24:MI TZH"))');
 ERROR:  cannot convert value from time to timetz without timezone usage
-HINT:  use *_tz() function for timezone support
+HINT:  Use *_tz() function for timezone support.
 select jsonb_path_query_tz(
 	'["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
 	'$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
@@ -2087,17 +2087,17 @@ select jsonb_path_query(
 	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
 	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
 ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
-HINT:  use *_tz() function for timezone support
+HINT:  Use *_tz() function for timezone support.
 select jsonb_path_query(
 	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
 	'$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
 ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
-HINT:  use *_tz() function for timezone support
+HINT:  Use *_tz() function for timezone support.
 select jsonb_path_query(
 	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
 	'$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
 ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
-HINT:  use *_tz() function for timezone support
+HINT:  Use *_tz() function for timezone support.
 select jsonb_path_query_tz(
 	'["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
 	'$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
@@ -2134,17 +2134,17 @@ select jsonb_path_query(
 	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
 	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
 ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
-HINT:  use *_tz() function for timezone support
+HINT:  Use *_tz() function for timezone support.
 select jsonb_path_query(
 	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
 	'$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
 ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
-HINT:  use *_tz() function for timezone support
+HINT:  Use *_tz() function for timezone support.
 select jsonb_path_query(
 	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
 	'$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
 ERROR:  cannot convert value from timestamp to timestamptz without timezone usage
-HINT:  use *_tz() function for timezone support
+HINT:  Use *_tz() function for timezone support.
 select jsonb_path_query_tz(
 	'["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
 	'$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
-- 
2.7.4