From ad1eea822bab92118ee51ba6961c87d89d48059b Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Fri, 1 Mar 2019 03:15:18 +0300
Subject: [PATCH 03/13] Implementation of datetime in jsonpath

---
 doc/src/sgml/func.sgml                       |  49 +-
 src/backend/utils/adt/date.c                 |  59 +-
 src/backend/utils/adt/formatting.c           | 870 +++++++++++++++++++++------
 src/backend/utils/adt/json.c                 |  32 +-
 src/backend/utils/adt/jsonb.c                |  27 +-
 src/backend/utils/adt/jsonb_util.c           |  20 +
 src/backend/utils/adt/jsonpath.c             |  23 +
 src/backend/utils/adt/jsonpath_exec.c        | 436 ++++++++++++++
 src/backend/utils/adt/jsonpath_gram.y        |  16 +
 src/backend/utils/adt/jsonpath_scan.l        |   1 +
 src/backend/utils/adt/timestamp.c            |  59 +-
 src/backend/utils/errcodes.txt               |   1 +
 src/include/utils/date.h                     |   6 +
 src/include/utils/datetime.h                 |   4 +
 src/include/utils/formatting.h               |   3 +
 src/include/utils/jsonapi.h                  |   3 +-
 src/include/utils/jsonb.h                    |  23 +-
 src/include/utils/jsonpath.h                 |   1 +
 src/include/utils/timestamp.h                |   3 +
 src/test/regress/expected/horology.out       |  79 +++
 src/test/regress/expected/jsonb_jsonpath.out | 535 ++++++++++++++++
 src/test/regress/expected/jsonpath.out       |  18 +
 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/jsonb_jsonpath.sql      | 148 +++++
 src/test/regress/sql/jsonpath.sql            |   3 +
 src/test/regress/sql/timestamp.sql           |   8 +
 src/test/regress/sql/timestamptz.sql         |   8 +
 29 files changed, 2239 insertions(+), 235 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 275fb3f..50299de 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -5994,6 +5994,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>
@@ -11413,9 +11437,9 @@ table2-mapping
    listed in <xref linkend="functions-sqljson-path-operators"/>.
    Each method must be preceded by a dot, while arithmetic and boolean
    operators are separated from the operands by spaces. For example,
-   you can get an array size:
+   you can convert a text string into a datetime value:
 <programlisting>
-'$.track.segments.size()'
+'$.track.segments[*]."start time".datetime()'
 </programlisting>
    For more examples of using <type>jsonpath</type> operators
    and methods within path expressions, see
@@ -11667,6 +11691,27 @@ table2-mapping
         <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>datetime(<replaceable>template</replaceable>, <replaceable>default_tz</replaceable>)</literal></entry>
+        <entry>Datetime value converted from a string with a specified template and default timezone</entry>
+        <entry><literal>["12:30 -02", "18:40"]</literal></entry>
+        <entry><literal>$[*].datetime("HH24:MI TZH", "+03:00")</literal></entry>
+        <entry><literal>"12:30:00-02:00", "18:40:00+03:00"</literal></entry>
+       </row>
+       <row>
         <entry><literal>keyvalue()</literal></entry>
         <entry>
           Sequence of object's key-value pairs represented as array of objects
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index cf5a1c6..5862297 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -40,11 +40,15 @@
 #error -ffast-math is known to break this code
 #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);
-
+#define date_ereport(res, msg) do { \
+	if (error) \
+	{ \
+		*error = true; \
+		return (res); \
+	} \
+	else \
+		ereport(ERROR, msg); \
+} while (0)
 
 /* common code for timetypmodin and timetztypmodin */
 static int32
@@ -567,9 +571,8 @@ date_mii(PG_FUNCTION_ARGS)
  * Internal routines for promoting date to timestamp and timestamp with
  * time zone
  */
-
-static Timestamp
-date2timestamp(DateADT dateVal)
+Timestamp
+date2timestamp_internal(DateADT dateVal, bool *error)
 {
 	Timestamp	result;
 
@@ -585,9 +588,9 @@ 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")));
+			date_ereport(0,
+						 (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;
@@ -597,7 +600,13 @@ date2timestamp(DateADT dateVal)
 }
 
 static TimestampTz
-date2timestamptz(DateADT dateVal)
+date2timestamp(DateADT dateVal)
+{
+	return date2timestamp_internal(dateVal, NULL);
+}
+
+TimestampTz
+date2timestamptz_internal(DateADT dateVal, int *tzp, bool *error)
 {
 	TimestampTz result;
 	struct pg_tm tt,
@@ -616,16 +625,16 @@ 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")));
+			date_ereport(0,
+						 (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));
 		tm->tm_hour = 0;
 		tm->tm_min = 0;
 		tm->tm_sec = 0;
-		tz = DetermineTimeZoneOffset(tm, session_timezone);
+		tz = tzp ? *tzp : DetermineTimeZoneOffset(tm, session_timezone);
 
 		result = dateVal * USECS_PER_DAY + tz * USECS_PER_SEC;
 
@@ -634,14 +643,20 @@ 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")));
+			date_ereport(0,
+						 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+						  errmsg("date out of range for timestamp")));
 	}
 
 	return result;
 }
 
+static TimestampTz
+date2timestamptz(DateADT dateVal)
+{
+	return date2timestamptz_internal(dateVal, NULL, NULL);
+}
+
 /*
  * date2timestamp_no_overflow
  *
@@ -1211,7 +1226,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 +1402,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 +1980,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 df1db7b..04382d6 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,19 @@ 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
+
+#define dch_ereport(res, ...) do { \
+	if (error) { \
+		*error = true; \
+		return (res); \
+	} else { \
+		ereport(ERROR, (__VA_ARGS__)); \
+	} \
+} while (0)
 
 /* ----------
  * Functions
@@ -977,7 +1016,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 bool DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
+			  bool strict, bool *error);
 
 #ifdef DEBUG_TO_FROM_CHAR
 static void dump_index(const KeyWord *k, const int *index);
@@ -988,14 +1028,16 @@ 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 bool from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode,
+				   bool *error);
+static bool from_char_set_int(int *dest, const int value, const FormatNode *node, bool *error);
+static int	from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node, bool *error);
+static int	from_char_parse_int(int *dest, char **src, FormatNode *node, bool *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 void do_to_timestamp(text *date_txt, text *fmt,
-				struct pg_tm *tm, fsec_t *fsec);
+static int	from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node, bool *error);
+static bool do_to_timestamp(text *date_txt, text *fmt, bool strict,
+				struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags,
+				bool *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);
@@ -2168,21 +2210,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 'error' is NULL, then errors are thrown, else '*error' is set and false
+ * is returned.
  */
-static void
-from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode)
+static bool
+from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode, bool *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.")));
+			dch_ereport(false,
+						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 true;
 }
 
 /*
@@ -2190,18 +2237,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 'error' is NULL, then errors are thrown, else '*error' is set and false
+ * is returned.
  */
-static void
-from_char_set_int(int *dest, const int value, const FormatNode *node)
+static bool
+from_char_set_int(int *dest, const int value, const FormatNode *node,
+				  bool *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.")));
+		dch_ereport(false,
+					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;
+
+	return true;
 }
 
 /*
@@ -2223,9 +2277,14 @@ 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 'error' is NULL, then errors are thrown, else '*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 *error)
 {
 	long		result;
 	char		copy[DCH_MAX_ITEM_SIZ + 1];
@@ -2258,50 +2317,56 @@ 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.")));
+			dch_ereport(-1,
+						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.")));
+			dch_ereport(-1,
+						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.")));
+		dch_ereport(-1,
+					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)));
+		dch_ereport(-1,
+					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);
+	{
+		if (!from_char_set_int(dest, (int) result, node, error))
+			return -1;			/* error */
+	}
+
 	return *src - init;
 }
 
@@ -2315,9 +2380,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 *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, error);
 }
 
 /* ----------
@@ -2400,11 +2465,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 'error' is NULL, otherwise
+ * set '*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 *error)
 {
 	int			len;
 
@@ -2416,12 +2482,12 @@ 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.")));
+		dch_ereport(-1,
+					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;
@@ -2514,18 +2580,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 +
@@ -3007,19 +3087,26 @@ 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.
+ *
+ * If 'error' is NULL, then errors are thrown, else '*error' is set and false
+ * is returned.
  * ----------
  */
-static void
-DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
+static bool
+DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict,
+			  bool *error)
 {
 	FormatNode *n;
 	char	   *s;
 	int			len,
 				value;
 	bool		fx_mode = false;
+
 	/* number of extra skipped characters (more than given in format string) */
 	int			extra_skip = 0;
 
@@ -3095,7 +3182,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 			continue;
 		}
 
-		from_char_set_mode(out, n->key->date_mode);
+		if (!from_char_set_mode(out, n->key->date_mode, error))
+			return false;		/* error */
 
 		switch (n->key->id)
 		{
@@ -3106,40 +3194,49 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 			case DCH_P_M:
 			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);
+				if (from_char_seq_search(&value, &s, ampm_strings_long,
+										 ALL_UPPER, n->key->len, n,
+										 error) < 0 ||
+					!from_char_set_int(&out->pm, value % 2, n, error))
+					return false;	/* error */
 				out->clock = CLOCK_12_HOUR;
 				break;
 			case DCH_AM:
 			case DCH_PM:
 			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);
+				if (from_char_seq_search(&value, &s, ampm_strings, ALL_UPPER,
+										 n->key->len, n, error) < 0 ||
+					!from_char_set_int(&out->pm, value % 2, n, error))
+					return false;
 				out->clock = CLOCK_12_HOUR;
 				break;
 			case DCH_HH:
 			case DCH_HH12:
-				from_char_parse_int_len(&out->hh, &s, 2, n);
+				if (from_char_parse_int_len(&out->hh, &s, 2, n, error) < 0)
+					return false;
 				out->clock = CLOCK_12_HOUR;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_HH24:
-				from_char_parse_int_len(&out->hh, &s, 2, n);
+				if (from_char_parse_int_len(&out->hh, &s, 2, n, error) < 0)
+					return false;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_MI:
-				from_char_parse_int(&out->mi, &s, n);
+				if (from_char_parse_int(&out->mi, &s, n, error) < 0)
+					return false;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_SS:
-				from_char_parse_int(&out->ss, &s, n);
+				if (from_char_parse_int(&out->ss, &s, n, error) < 0)
+					return false;
 				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, error);
+				if (len < 0)
+					return false;
 
 				/*
 				 * 25 is 0.25 and 250 is 0.25 too; 025 is 0.025 and not 0.25
@@ -3149,8 +3246,20 @@ 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, error);
+				if (len < 0)
+					return false;
 
 				out->us *= len == 1 ? 100000 :
 					len == 2 ? 10000 :
@@ -3161,18 +3270,20 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_SSSS:
-				from_char_parse_int(&out->ssss, &s, n);
+				if (from_char_parse_int(&out->ssss, &s, n, error) < 0)
+					return false;
 				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)));
+				dch_ereport(false,
+							errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							errmsg("formatting field \"%s\" is only supported in to_char",
+								   n->key->name));
 				break;
 			case DCH_TZH:
+
 				/*
 				 * Value of TZH might be negative.  And the issue is that we
 				 * might swallow minus sign as the separator.  So, if we have
@@ -3192,82 +3303,97 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 						out->tzsign = +1;
 				}
 
-				from_char_parse_int_len(&out->tzh, &s, 2, n);
+				if (from_char_parse_int_len(&out->tzh, &s, 2, n, error) < 0)
+					return false;
 				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);
+				if (from_char_parse_int_len(&out->tzm, &s, 2, n, error) < 0)
+					return false;
 				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);
+				if (from_char_seq_search(&value, &s, adbc_strings_long,
+										 ALL_UPPER, n->key->len, n,
+										 error) < 0 ||
+					!from_char_set_int(&out->bc, value % 2, n, error))
+					return false;
 				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);
+				if (from_char_seq_search(&value, &s, adbc_strings, ALL_UPPER,
+										 n->key->len, n, error) < 0 ||
+					!from_char_set_int(&out->bc, value % 2, n, error))
+					return false;
 				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);
+				if (from_char_seq_search(&value, &s, months_full, ONE_UPPER,
+										 MAX_MONTH_LEN, n, error) < 0 ||
+					!from_char_set_int(&out->mm, value + 1, n, error))
+					return false;
 				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);
+				if (from_char_seq_search(&value, &s, months, ONE_UPPER,
+										 MAX_MON_LEN, n, error) < 0 ||
+					!from_char_set_int(&out->mm, value + 1, n, error))
+					return false;
 				break;
 			case DCH_MM:
-				from_char_parse_int(&out->mm, &s, n);
+				if (from_char_parse_int(&out->mm, &s, n, error) < 0)
+					return false;
 				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);
+				if (from_char_seq_search(&value, &s, days, ONE_UPPER,
+										 MAX_DAY_LEN, n, error) < 0 ||
+					!from_char_set_int(&out->d, value, n, error))
+					return false;
 				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);
+				if (from_char_seq_search(&value, &s, days, ONE_UPPER,
+										 MAX_DY_LEN, n, error) < 0 ||
+					!from_char_set_int(&out->d, value, n, error))
+					return false;
 				out->d++;
 				break;
 			case DCH_DDD:
-				from_char_parse_int(&out->ddd, &s, n);
+				if (from_char_parse_int(&out->ddd, &s, n, error) < 0)
+					return false;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_IDDD:
-				from_char_parse_int_len(&out->ddd, &s, 3, n);
+				if (from_char_parse_int_len(&out->ddd, &s, 3, n, error) < 0)
+					return false;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_DD:
-				from_char_parse_int(&out->dd, &s, n);
+				if (from_char_parse_int(&out->dd, &s, n, error) < 0)
+					return false;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_D:
-				from_char_parse_int(&out->d, &s, n);
+				if (from_char_parse_int(&out->d, &s, n, error) < 0)
+					return false;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_ID:
-				from_char_parse_int_len(&out->d, &s, 1, n);
+				if (from_char_parse_int_len(&out->d, &s, 1, n, error) < 0)
+					return false;
 				/* Shift numbering to match Gregorian where Sunday = 1 */
 				if (++out->d > 7)
 					out->d = 1;
@@ -3275,7 +3401,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 				break;
 			case DCH_WW:
 			case DCH_IW:
-				from_char_parse_int(&out->ww, &s, n);
+				if (from_char_parse_int(&out->ww, &s, n, error) < 0)
+					return false;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Q:
@@ -3290,11 +3417,13 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 				 * 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);
+				if (from_char_parse_int((int *) NULL, &s, n, error) < 0)
+					return false;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_CC:
-				from_char_parse_int(&out->cc, &s, n);
+				if (from_char_parse_int(&out->cc, &s, n, error) < 0)
+					return false;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_Y_YYY:
@@ -3306,11 +3435,12 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 
 					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\"")));
+						dch_ereport(false,
+									errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+									errmsg("invalid input string for \"Y,YYY\""));
 					years += (millennia * 1000);
-					from_char_set_int(&out->year, years, n);
+					if (!from_char_set_int(&out->year, years, n, error))
+						return false;
 					out->yysz = 4;
 					s += nch;
 					SKIP_THth(s, n->suffix);
@@ -3318,47 +3448,63 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 				break;
 			case DCH_YYYY:
 			case DCH_IYYY:
-				from_char_parse_int(&out->year, &s, n);
+				if (from_char_parse_int(&out->year, &s, n, error) < 0)
+					return false;
 				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, error);
+				if (len < 0)
+					return false;
+				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, error);
+				if (len < 0)
+					return false;
+				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, error);
+				if (len < 0)
+					return false;
+				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);
+				if (from_char_seq_search(&value, &s, rm_months_upper,
+										 ALL_UPPER, MAX_RM_LEN, n, error) < 0 ||
+					!from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n,
+									   error))
+					return false;
 				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);
+				if (from_char_seq_search(&value, &s, rm_months_lower,
+										 ALL_LOWER, MAX_RM_LEN, n, error) < 0 ||
+					!from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n,
+									   error))
+					return false;
 				break;
 			case DCH_W:
-				from_char_parse_int(&out->w, &s, n);
+				if (from_char_parse_int(&out->w, &s, n, error) < 0)
+					return false;
 				SKIP_THth(s, n->suffix);
 				break;
 			case DCH_J:
-				from_char_parse_int(&out->j, &s, n);
+				if (from_char_parse_int(&out->j, &s, n, error) < 0)
+					return false;
 				SKIP_THth(s, n->suffix);
 				break;
 		}
@@ -3374,6 +3520,25 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 			}
 		}
 	}
+
+	if (strict)
+	{
+		if (n->type != NODE_TYPE_END)
+			dch_ereport(false,
+						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')
+			dch_ereport(false,
+						errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+						errmsg("trailing characters remain in input string "
+							   "after datetime format"));
+	}
+
+	return true;
 }
 
 /*
@@ -3394,6 +3559,115 @@ DCH_prevent_counter_overflow(void)
 	}
 }
 
+/*
+ * Get mask of date/time/zone components present in format nodes.
+ *
+ * If 'error' is NULL, then errors are thrown, else '*error' is set and 0
+ * is returned.
+ */
+static int
+DCH_datetime_type(FormatNode *node, bool *error)
+{
+	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:
+				dch_ereport(0,
+							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)
@@ -3666,6 +3940,72 @@ interval_to_char(PG_FUNCTION_ARGS)
 	PG_RETURN_TEXT_P(res);
 }
 
+/*
+ * Decode the specified or default timezone, if any, or use the session
+ * timezone if it is allowed.
+ */
+static bool
+get_timezone(struct pg_tm *tm, char *default_tz_name, text *date_txt,
+			 const char *type_name, bool allow_session_timezone,
+			 int *tz, bool *error)
+{
+	/* Use the specified or default time zone, if any. */
+	char	   *tz_name =
+		tm->tm_zone ? unconstify(char *, tm->tm_zone) : default_tz_name;
+
+	if (tz_name)
+	{
+		int			dterr = DecodeTimezone(tz_name, tz);
+
+		if (dterr)
+		{
+			if (error)
+			{
+				*error = true;
+				return false;
+			}
+
+			DateTimeParseError(dterr,
+							   date_txt ? text_to_cstring(date_txt) : tz_name,
+							   type_name);
+		}
+	}
+	else if (*tz == PG_INT32_MIN)
+	{
+		if (!allow_session_timezone)
+			dch_ereport(false,
+						errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+						errmsg("missing time-zone in input string for type %s",
+							   type_name));
+
+		*tz = DetermineTimeZoneOffset(tm, session_timezone);
+	}
+
+	return true;
+}
+
+/*
+ * Convert pg_tm to timestamp ('tz' is NULL) or timestamptz ('tz' is not NULL)
+ * using the specified fractional precision 'typmod'.
+ */
+static inline Timestamp
+tm_to_timestamp(struct pg_tm *tm, fsec_t fsec, int32 typmod, int *tz,
+				bool *error)
+{
+	Timestamp	result;
+
+	if (tm2timestamp(tm, fsec, tz, &result) != 0)
+		dch_ereport(ERROR,
+					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+					 errmsg("timestamp out of range")));
+
+	/* Use the specified fractional precision, if any. */
+	if (typmod != -1)
+		AdjustTimestampForTypmodError(&result, typmod, error);
+
+	return result;
+}
+
 /* ---------------------
  * TO_TIMESTAMP()
  *
@@ -3678,30 +4018,45 @@ to_timestamp(PG_FUNCTION_ARGS)
 {
 	text	   *date_txt = PG_GETARG_TEXT_PP(0);
 	text	   *fmt = PG_GETARG_TEXT_PP(1);
-	Timestamp	result;
-	int			tz;
+	int			tz = PG_INT32_MIN;
 	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, NULL);
 
 	/* Use the specified time zone, if any. */
-	if (tm.tm_zone)
-	{
-		int			dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), &tz);
+	get_timezone(&tm, NULL, date_txt, "timestamp", true, &tz, NULL);
 
-		if (dterr)
-			DateTimeParseError(dterr, text_to_cstring(date_txt), "timestamptz");
-	}
-	else
-		tz = DetermineTimeZoneOffset(&tm, session_timezone);
+	PG_RETURN_DATUM(tm_to_timestamp(&tm, fsec, fprec ? fprec : -1, &tz, NULL));
+}
 
-	if (tm2timestamp(&tm, fsec, &tz, &result) != 0)
-		ereport(ERROR,
-				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-				 errmsg("timestamp out of range")));
+/*
+ * Convert pg_tm to date with out of range checking.
+ */
+static DateADT
+tm_to_date(struct pg_tm *tm, text *date_txt, bool *error)
+{
+	DateADT		result;
+
+	/* Prevent overflow in Julian-day routines */
+	if (!IS_VALID_JULIAN(tm->tm_year, tm->tm_mon, tm->tm_mday))
+		dch_ereport((Datum) 0,
+					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))
+		dch_ereport((Datum) 0,
+					errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+					errmsg("date out of range: \"%s\"",
+						   text_to_cstring(date_txt)));
 
-	PG_RETURN_TIMESTAMP(result);
+	return DateADTGetDatum(result);
 }
 
 /* ----------
@@ -3714,36 +4069,149 @@ to_date(PG_FUNCTION_ARGS)
 {
 	text	   *date_txt = PG_GETARG_TEXT_PP(0);
 	text	   *fmt = PG_GETARG_TEXT_PP(1);
-	DateADT		result;
 	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, NULL);
 
-	/* 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))));
+	PG_RETURN_DATUM(tm_to_date(&tm, date_txt, NULL));
+}
+
+/*
+ * Convert pg_tm to timetz using the specified fractional precision 'typmod'
+ * and timezone 'tz'.
+ */
+static Datum
+tm_to_timetz(struct pg_tm *tm, fsec_t fsec, int32 typmod, int *tz, bool *error)
+{
+	TimeTzADT  *result = palloc(sizeof(TimeTzADT));
 
-	result = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) - POSTGRES_EPOCH_JDATE;
+	if (tm2timetz(tm, fsec, *tz, result) != 0)
+		dch_ereport((Datum) 0,
+					errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+					errmsg("timetz out of range"));
 
-	/* 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))));
+	if (typmod != -1)
+		AdjustTimeForTypmod(&result->time, typmod);
 
-	PG_RETURN_DATEADT(result);
+	return TimeTzADTPGetDatum(result);
+}
+
+/*
+ * Convert pg_tm to time using the specified fractional precision 'typmod'.
+ */
+static Datum
+tm_to_time(struct pg_tm *tm, fsec_t fsec, int32 typmod, bool *error)
+{
+	TimeADT		result;
+
+	if (tm2time(tm, fsec, &result) != 0)
+		dch_ereport((Datum) 0,
+					errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+					errmsg("time out of range"));
+
+	if (typmod != -1)
+		AdjustTimeForTypmod(&result, typmod);
+
+	return TimeADTGetDatum(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.
+ *
+ * If 'error' is NULL, then errors are thrown, else '*error' is set and
+ * ((Datum) 0) is returned.
+ */
+Datum
+parse_datetime(text *date_txt, text *fmt, char *tzname, bool strict,
+			   Oid *typid, int32 *typmod, int *tz, bool *error)
+{
+	struct pg_tm tm;
+	fsec_t		fsec;
+	int			fprec = 0;
+	int			flags;
+
+	if (!do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags,
+						 error))
+		return (Datum) 0;
+
+	/* Save default time-zone for non-zoned types. */
+	if (!(flags & DCH_ZONED) && tzname &&
+		!get_timezone(&tm, tzname, NULL, "timezone", false, tz, error))
+		return (Datum) 0;
+
+	if (flags & DCH_DATED)
+	{
+		if (flags & DCH_TIMED)
+		{
+			*typmod = fprec ? fprec : -1;	/* fractional part precision */
+
+			if (flags & DCH_ZONED)
+			{
+				if (!get_timezone(&tm, tzname, NULL, "timestamptz", false, tz,
+								  error))
+					return (Datum) 0;
+
+				*typid = TIMESTAMPTZOID;
+				return tm_to_timestamp(&tm, fsec, *typmod, tz, error);
+			}
+			else
+			{
+				*typid = TIMESTAMPOID;
+				return tm_to_timestamp(&tm, fsec, *typmod, NULL, error);
+			}
+		}
+		else
+		{
+			if (flags & DCH_ZONED)
+				dch_ereport((Datum) 0,
+							errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							errmsg("datetime format is zoned but not timed"));
+
+			*typid = DATEOID;
+			*typmod = -1;
+			return tm_to_date(&tm, date_txt, error);
+		}
+	}
+	else if (flags & DCH_TIMED)
+	{
+		*typmod = fprec ? fprec : -1;	/* fractional part precision */
+
+		if (flags & DCH_ZONED)
+		{
+			if (!get_timezone(&tm, tzname, NULL, "timetz", false, tz, error))
+				return (Datum) 0;
+
+			*typid = TIMETZOID;
+			return tm_to_timetz(&tm, fsec, *typmod, tz, error);
+		}
+		else
+		{
+			*typid = TIMEOID;
+			return tm_to_time(&tm, fsec, *typmod, error);
+		}
+	}
+	else
+	{
+		dch_ereport((Datum) 0,
+					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
@@ -3751,10 +4219,18 @@ 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.
+ *
+ * If 'error' is NULL, then errors are thrown, else '*error' is set and false
+ * is returned.
  */
-static void
-do_to_timestamp(text *date_txt, text *fmt,
-				struct pg_tm *tm, fsec_t *fsec)
+static bool
+do_to_timestamp(text *date_txt, text *fmt, bool strict, struct pg_tm *tm,
+				fsec_t *fsec, int *fprec, int *flags, bool *error)
 {
 	FormatNode *format;
 	TmFromChar	tmfc;
@@ -3807,11 +4283,18 @@ 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, error);
 
 		pfree(fmt_str);
+
+		if (flags && (!error || !*error))
+			*flags = DCH_datetime_type(format, error);
+
 		if (!incache)
 			pfree(format);
+
+		if (error && *error)
+			goto err;
 	}
 
 	DEBUG_TMFC(&tmfc);
@@ -3840,11 +4323,15 @@ do_to_timestamp(text *date_txt, text *fmt,
 	if (tmfc.clock == CLOCK_12_HOUR)
 	{
 		if (tm->tm_hour < 1 || tm->tm_hour > HOURS_PER_DAY / 2)
+		{
+			if (error)
+				goto err;
 			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;
@@ -3948,9 +4435,13 @@ do_to_timestamp(text *date_txt, text *fmt,
 		 */
 
 		if (!tm->tm_year && !tmfc.bc)
+		{
+			if (error)
+				goto err;
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
 					 errmsg("cannot calculate day of year without year information")));
+		}
 
 		if (tmfc.mode == FROM_CHAR_DATE_ISOWEEK)
 		{
@@ -3991,6 +4482,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)
@@ -4000,6 +4493,9 @@ do_to_timestamp(text *date_txt, text *fmt,
 
 		if (dterr != 0)
 		{
+			if (error)
+				goto err;
+
 			/*
 			 * Force the error to be DTERR_FIELD_OVERFLOW even if ValidateDate
 			 * said DTERR_MD_FIELD_OVERFLOW, because we don't want to print an
@@ -4014,7 +4510,12 @@ do_to_timestamp(text *date_txt, text *fmt,
 		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)
+	{
+		if (error)
+			goto err;
+
 		DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp");
+	}
 
 	/* Save parsed time-zone into tm->tm_zone if it was specified */
 	if (tmfc.tzsign)
@@ -4023,7 +4524,12 @@ do_to_timestamp(text *date_txt, text *fmt,
 
 		if (tmfc.tzh < 0 || tmfc.tzh > MAX_TZDISP_HOUR ||
 			tmfc.tzm < 0 || tmfc.tzm >= MINS_PER_HOUR)
+		{
+			if (error)
+				goto err;
+
 			DateTimeParseError(DTERR_TZDISP_OVERFLOW, date_str, "timestamp");
+		}
 
 		tz = psprintf("%c%02d:%02d",
 					  tmfc.tzsign > 0 ? '+' : '-', tmfc.tzh, tmfc.tzm);
@@ -4034,6 +4540,12 @@ do_to_timestamp(text *date_txt, text *fmt,
 	DEBUG_TM(tm);
 
 	pfree(date_str);
+	return true;
+
+err:
+	*error = true;
+	pfree(date_str);
+	return false;
 }
 
 
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index de0d072..5239903 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 7af4091..87d3a8d 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 d8276ab..5618d39 100644
--- a/src/backend/utils/adt/jsonb_util.c
+++ b/src/backend/utils/adt/jsonb_util.c
@@ -15,8 +15,11 @@
 
 #include "access/hash.h"
 #include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
 #include "miscadmin.h"
 #include "utils/builtins.h"
+#include "utils/datetime.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/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 0696b04..450e5e9 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -282,6 +282,7 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
 		case jpiDiv:
 		case jpiMod:
 		case jpiStartsWith:
+		case jpiDatetime:
 			{
 				int32		left,
 							right;
@@ -684,6 +685,22 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
 		case jpiDouble:
 			appendBinaryStringInfo(buf, ".double()", 9);
 			break;
+		case jpiDatetime:
+			appendBinaryStringInfo(buf, ".datetime(", 10);
+			if (v->content.args.left)
+			{
+				jspGetLeftArg(v, &elem);
+				printJsonPathItem(buf, &elem, false, false);
+
+				if (v->content.args.right)
+				{
+					appendBinaryStringInfo(buf, ", ", 2);
+					jspGetRightArg(v, &elem);
+					printJsonPathItem(buf, &elem, false, false);
+				}
+			}
+			appendStringInfoChar(buf, ')');
+			break;
 		case jpiKeyValue:
 			appendBinaryStringInfo(buf, ".keyvalue()", 11);
 			break;
@@ -746,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;
@@ -866,6 +885,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
 		case jpiLessOrEqual:
 		case jpiGreaterOrEqual:
 		case jpiStartsWith:
+		case jpiDatetime:
 			read_int32(v->content.args.left, base, pos);
 			read_int32(v->content.args.right, base, pos);
 			break;
@@ -953,6 +973,7 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
 			   v->type == jpiFloor ||
 			   v->type == jpiCeiling ||
 			   v->type == jpiDouble ||
+			   v->type == jpiDatetime ||
 			   v->type == jpiKeyValue ||
 			   v->type == jpiStartsWith);
 
@@ -980,6 +1001,7 @@ jspGetLeftArg(JsonPathItem *v, JsonPathItem *a)
 		   v->type == jpiMul ||
 		   v->type == jpiDiv ||
 		   v->type == jpiMod ||
+		   v->type == jpiDatetime ||
 		   v->type == jpiStartsWith);
 
 	jspInitByBuffer(a, v->base, v->content.args.left);
@@ -1001,6 +1023,7 @@ jspGetRightArg(JsonPathItem *v, JsonPathItem *a)
 		   v->type == jpiMul ||
 		   v->type == jpiDiv ||
 		   v->type == jpiMod ||
+		   v->type == jpiDatetime ||
 		   v->type == jpiStartsWith);
 
 	jspInitByBuffer(a, v->base, v->content.args.right);
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index c6a5073..7d84ef9 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"
@@ -86,6 +87,8 @@
 #define ERRMSG_SINGLETON_JSON_ITEM_REQUIRED	"singleton SQL/JSON item required"
 #define ERRMSG_NON_NUMERIC_JSON_ITEM		"non-numeric SQL/JSON item"
 #define ERRMSG_INVALID_JSON_SUBSCRIPT		"invalid SQL/JSON subscript"
+#define ERRMSG_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION	\
+	"invalid argument for SQL/JSON datetime function"
 
 /*
  * Represents "base object" and it's "id" for .keyvalue() evaluation.
@@ -256,6 +259,13 @@ static int	JsonbType(JsonbValue *jb);
 static JsonbValue *getScalar(JsonbValue *scalar, enum jbvType type);
 static JsonbValue *wrapItemsInArray(const JsonValueList *items);
 
+static bool tryToParseDatetime(text *fmt, text *datetime, char *tzname,
+				   bool strict, Datum *value, Oid *typid,
+				   int32 *typmod, int *tzp, bool throwErrors);
+static int compareDatetime(Datum val1, Oid typid1, int tz1,
+				Datum val2, Oid typid2, int tz2,
+				bool *error);
+
 /****************** User interface to JsonPath executor ********************/
 
 /*
@@ -1056,6 +1066,177 @@ recursiveExecuteUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
 			}
 			break;
 
+		case jpiDatetime:
+			{
+				JsonbValue	jbvbuf;
+				Datum		value;
+				text	   *datetime;
+				Oid			typid;
+				int32		typmod = -1;
+				int			tz = PG_INT32_MIN;
+				bool		hasNext;
+				char	   *tzname = NULL;
+
+				if (unwrap && JsonbType(jb) == jbvArray)
+					return recursiveExecuteUnwrapArray(cxt, jsp, jb, found,
+													   false);
+
+				if (!(jb = getScalar(jb, jbvString)))
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+										  errmsg(ERRMSG_INVALID_ARGUMENT_FOR_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.args.left)
+				{
+					text	   *template;
+					char	   *template_str;
+					int			template_len;
+
+					jspGetLeftArg(jsp, &elem);
+
+					if (elem.type != jpiString)
+						elog(ERROR, "invalid jsonpath item type for "
+							 ".datetime() argument");
+
+					template_str = jspGetString(&elem, &template_len);
+
+					if (jsp->content.args.right)
+					{
+						JsonValueList tzlist = {0};
+						JsonPathExecResult tzres;
+						JsonbValue *tzjbv;
+
+						jspGetRightArg(jsp, &elem);
+						tzres = recursiveExecute(cxt, &elem, jb, &tzlist);
+						if (jperIsError(tzres))
+							return tzres;
+
+						if (JsonValueListLength(&tzlist) != 1 ||
+							((tzjbv = JsonValueListHead(&tzlist))->type != jbvString &&
+							 tzjbv->type != jbvNumeric))
+							RETURN_ERROR(ereport(ERROR,
+												 (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+												  errmsg(ERRMSG_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+												  errdetail("timezone argument of "
+															"jsonpath item method .%s() "
+															"is not a singleton string or number",
+															jspOperationName(jsp->type)))));
+
+						if (tzjbv->type == jbvString)
+							tzname = pnstrdup(tzjbv->val.string.val,
+											  tzjbv->val.string.len);
+						else
+						{
+							bool		error = false;
+
+							tz = numeric_int4_error(tzjbv->val.numeric, &error);
+
+							if (error || tz == PG_INT32_MIN)
+								RETURN_ERROR(ereport(ERROR,
+													 (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+													  errmsg(ERRMSG_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+													  errdetail("timezone argument of "
+																"jsonpath item method .%s() "
+																"is out of integer range",
+																jspOperationName(jsp->type)))));
+
+							tz = -tz;
+						}
+					}
+
+					if (template_len)
+					{
+						template = cstring_to_text_with_len(template_str,
+															template_len);
+
+						if (tryToParseDatetime(template, datetime, tzname, false,
+											   &value, &typid, &typmod,
+											   &tz, jspThrowErrors(cxt)))
+							res = jperOk;
+						else
+							res = jperError;
+					}
+				}
+
+				if (res == jperNotFound)
+				{
+					/* 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;
+
+					for (i = 0; i < lengthof(fmt_str); i++)
+					{
+						if (!fmt_txt[i])
+						{
+							MemoryContext oldcxt =
+							MemoryContextSwitchTo(TopMemoryContext);
+
+							fmt_txt[i] = cstring_to_text(fmt_str[i]);
+							MemoryContextSwitchTo(oldcxt);
+						}
+
+						if (tryToParseDatetime(fmt_txt[i], datetime, tzname,
+											   true, &value, &typid, &typmod,
+											   &tz, false))
+						{
+							res = jperOk;
+							break;
+						}
+					}
+
+					if (res == jperNotFound)
+						RETURN_ERROR(ereport(ERROR,
+											 (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+											  errmsg(ERRMSG_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+											  errdetail("unrecognized datetime format"),
+											  errhint("use datetime template "
+													  "argument for explicit "
+													  "format specification"))));
+				}
+
+				if (tzname)
+					pfree(tzname);
+
+				pfree(datetime);
+
+				if (jperIsError(res))
+					break;
+
+				hasNext = jspGetNext(jsp, &elem);
+
+				if (!hasNext && !found)
+					break;
+
+				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;
+
+				res = recursiveExecuteNext(cxt, jsp, &elem, jb, found, hasNext);
+			}
+			break;
+
 		case jpiKeyValue:
 			if (unwrap && JsonbType(jb) == jbvArray)
 				return recursiveExecuteUnwrapArray(cxt, jsp, jb, found, false);
@@ -2044,6 +2225,22 @@ compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2)
 							 jb2->val.string.val, jb2->val.string.len,
 							 DEFAULT_COLLATION_OID);
 			break;
+		case jbvDatetime:
+			{
+				bool		error = false;
+
+				cmp = compareDatetime(jb1->val.datetime.value,
+									  jb1->val.datetime.typid,
+									  jb1->val.datetime.tz,
+									  jb2->val.datetime.value,
+									  jb2->val.datetime.typid,
+									  jb2->val.datetime.tz,
+									  &error);
+
+				if (error)
+					return jpbUnknown;
+			}
+			break;
 
 		case jbvBinary:
 		case jbvArray:
@@ -2303,3 +2500,242 @@ wrapItemsInArray(const JsonValueList *items)
 
 	return pushJsonbValue(&ps, WJB_END_ARRAY, NULL);
 }
+
+static inline Datum
+time_to_timetz(Datum time, int tz, bool *error)
+{
+	TimeADT		tm = DatumGetTimeADT(time);
+	TimeTzADT  *result = palloc(sizeof(TimeTzADT));
+
+	if (tz == PG_INT32_MIN)
+	{
+		*error = true;
+		return (Datum) 0;
+	}
+
+	result->time = tm;
+	result->zone = tz;
+
+	return TimeTzADTPGetDatum(result);
+}
+
+static inline Datum
+date_to_timestamp(Datum date, bool *error)
+{
+	DateADT		dt = DatumGetDateADT(date);
+	Timestamp	ts = date2timestamp_internal(dt, error);
+
+	return TimestampGetDatum(ts);
+}
+
+static inline Datum
+date_to_timestamptz(Datum date, int tz, bool *error)
+{
+	DateADT		dt = DatumGetDateADT(date);
+	TimestampTz ts;
+
+	if (tz == PG_INT32_MIN)
+	{
+		*error = true;
+		return (Datum) 0;
+	}
+
+	ts = date2timestamptz_internal(dt, &tz, error);
+
+	return TimestampTzGetDatum(ts);
+}
+
+static inline Datum
+timestamp_to_timestamptz(Datum val, int tz, bool *error)
+{
+	Timestamp	ts = DatumGetTimestamp(val);
+	TimestampTz tstz;
+
+	if (tz == PG_INT32_MIN)
+	{
+		*error = true;
+		return (Datum) 0;
+	}
+
+	tstz = timestamp2timestamptz_internal(ts, &tz, error);
+
+	return TimestampTzGetDatum(tstz);
+}
+
+/*
+ * Cross-type comparison of two datetime SQL/JSON items.  If items are
+ * uncomparable, 'error' flag is set.
+ */
+static int
+compareDatetime(Datum val1, Oid typid1, int tz1,
+				Datum val2, Oid typid2, int tz2,
+				bool *error)
+{
+	PGFunction cmpfunc = NULL;
+
+	switch (typid1)
+	{
+		case DATEOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					cmpfunc = date_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					val1 = date_to_timestamp(val1, error);
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					val1 = date_to_timestamptz(val1, tz1, error);
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*error = true;
+					return 0;
+			}
+			break;
+
+		case TIMEOID:
+			switch (typid2)
+			{
+				case TIMEOID:
+					cmpfunc = time_cmp;
+
+					break;
+
+				case TIMETZOID:
+					val1 = time_to_timetz(val1, tz1, error);
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case DATEOID:
+				case TIMESTAMPOID:
+				case TIMESTAMPTZOID:
+					*error = true;
+					return 0;
+			}
+			break;
+
+		case TIMETZOID:
+			switch (typid2)
+			{
+				case TIMEOID:
+					val2 = time_to_timetz(val2, tz2, error);
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case TIMETZOID:
+					cmpfunc = timetz_cmp;
+
+					break;
+
+				case DATEOID:
+				case TIMESTAMPOID:
+				case TIMESTAMPTZOID:
+					*error = true;
+					return 0;
+			}
+			break;
+
+		case TIMESTAMPOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					val2 = date_to_timestamp(val2, error);
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					val1 = timestamp_to_timestamptz(val1, tz1, error);
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*error = true;
+					return 0;
+			}
+			break;
+
+		case TIMESTAMPTZOID:
+			switch (typid2)
+			{
+				case DATEOID:
+					val2 = date_to_timestamptz(val2, tz2, error);
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPOID:
+					val2 = timestamp_to_timestamptz(val2, tz2, error);
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMESTAMPTZOID:
+					cmpfunc = timestamp_cmp;
+
+					break;
+
+				case TIMEOID:
+				case TIMETZOID:
+					*error = true;
+					return 0;
+			}
+			break;
+
+		default:
+			elog(ERROR, "unrecognized SQL/JSON datetime type oid: %d",
+				 typid1);
+	}
+
+	if (*error)
+		return 0;
+
+	if (!cmpfunc)
+		elog(ERROR, "unrecognized SQL/JSON datetime type oid: %d",
+			 typid2);
+
+	*error = false;
+
+	return DatumGetInt32(DirectFunctionCall2(cmpfunc, val1, val2));
+}
+
+/*
+ * Try to parse datetime text with the specified datetime template and
+ * default time-zone 'tzname'.
+ * Returns 'value' datum, its type 'typid' and 'typmod'.
+ * Datetime error is rethrown with SQL/JSON errcode if 'throwErrors' is true.
+ */
+static bool
+tryToParseDatetime(text *fmt, text *datetime, char *tzname, bool strict,
+				   Datum *value, Oid *typid, int32 *typmod, int *tzp,
+				   bool throwErrors)
+{
+	bool		error = false;
+	int			tz = *tzp;
+
+	*value = parse_datetime(datetime, fmt, tzname, strict, typid, typmod,
+							&tz, throwErrors ? NULL : &error);
+
+	if (!error)
+		*tzp = tz;
+
+	return !error;
+}
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
index 183861f..1dd5978 100644
--- a/src/backend/utils/adt/jsonpath_gram.y
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -283,12 +283,14 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags)
 %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
 
@@ -434,9 +436,22 @@ accessor_op:
 	| array_accessor				{ $$ = $1; }
 	| '.' any_path					{ $$ = $2; }
 	| '.' method '(' ')'			{ $$ = makeItemType($2); }
+	| '.' DATETIME_P '(' opt_datetime_template ')'
+									{ $$ = makeItemBinary(jpiDatetime, $4, NULL); }
+	| '.' DATETIME_P '(' datetime_template ',' expr ')'
+									{ $$ = makeItemBinary(jpiDatetime, $4, $6); }
 	| '?' '(' predicate ')'			{ $$ = makeItemUnary(jpiFilter, $3); }
 	;
 
+datetime_template:
+	STRING_P						{ $$ = makeItemString(&$1); }
+	;
+
+opt_datetime_template:
+	datetime_template				{ $$ = $1; }
+	| /* EMPTY */					{ $$ = NULL; }
+	;
+
 key:
 	key_name						{ $$ = makeItemKey(&$1); }
 	;
@@ -459,6 +474,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 110ea21..52db47c 100644
--- a/src/backend/utils/adt/jsonpath_scan.l
+++ b/src/backend/utils/adt/jsonpath_scan.l
@@ -315,6 +315,7 @@ static keyword 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/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index e0ef2f7..04596e1 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -71,7 +71,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);
@@ -339,11 +338,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.
  */
-static void
-AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
+bool
+AdjustTimestampForTypmodError(Timestamp *time, int32 typmod, bool *error)
 {
 	static const int64 TimestampScales[MAX_TIMESTAMP_PRECISION + 1] = {
 		INT64CONST(1000000),
@@ -369,10 +368,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))
 		{
@@ -385,8 +392,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.
@@ -5179,8 +5193,8 @@ timestamp_timestamptz(PG_FUNCTION_ARGS)
 	PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
 }
 
-static TimestampTz
-timestamp2timestamptz(Timestamp timestamp)
+TimestampTz
+timestamp2timestamptz_internal(Timestamp timestamp, int *tzp, bool *error)
 {
 	TimestampTz result;
 	struct pg_tm tt,
@@ -5189,23 +5203,30 @@ 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;
 
-		tz = DetermineTimeZoneOffset(tm, session_timezone);
+	if (!timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL))
+	{
+		tz = tzp ? *tzp : 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 (error)
+		*error = true;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+				 errmsg("timestamp out of range")));
+
+	return 0;
+}
+
+static TimestampTz
+timestamp2timestamptz(Timestamp timestamp)
+{
+	return timestamp2timestamptz_internal(timestamp, NULL, NULL);
 }
 
 /* timestamptz_timestamp()
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/utils/date.h b/src/include/utils/date.h
index bec129a..06ebb7d 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -70,11 +70,17 @@ typedef struct
 /* date.c */
 extern int32 anytime_typmod_check(bool istz, int32 typmod);
 extern double date2timestamp_no_overflow(DateADT dateVal);
+extern Timestamp date2timestamp_internal(DateADT dateVal, bool *error);
+extern TimestampTz date2timestamptz_internal(DateADT dateVal, int *tzp,
+						  bool *error);
 extern void EncodeSpecialDate(DateADT dt, char *str);
 extern DateADT GetSQLCurrentDate(void);
 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 87f819e..7217312 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -338,4 +338,8 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
 					   int n);
 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 5b275dc..c9efdd1 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, bool *error);
+
 #endif
diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h
index b9993a9..9eda9a3 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 ec0355f..9698c46 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -238,7 +238,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,
 };
 
 /*
@@ -279,11 +287,20 @@ struct JsonbValue
 			int			len;
 			JsonbContainer *data;
 		}			binary;		/* Array or object, in on-disk format */
+
+		struct
+		{
+			Datum		value;
+			Oid			typid;
+			int32		typmod;
+			int			tz;
+		}			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.
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 14f837e..2170a70 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -77,6 +77,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/include/utils/timestamp.h b/src/include/utils/timestamp.h
index aeb89dc..8ae8e8f 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_internal(Timestamp timestamp,
+							   int *tzp, bool *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);
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/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
index 2202ffa..150a138 100644
--- a/src/test/regress/expected/jsonb_jsonpath.out
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -1468,6 +1468,541 @@ 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:  datetime format is not dated and not timed
+select jsonb_path_query('"12:34"', '$.datetime("aaa", 1)');
+ERROR:  datetime format is not dated and not timed
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", 1)');
+  jsonb_path_query   
+---------------------
+ "12:34:00+00:00:01"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", 10000000000000)');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  timezone argument of jsonpath item method .datetime() is out of integer range
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", 10000000000000)');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  timezone argument of jsonpath item method .datetime() is out of integer range
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", 2147483647)');
+    jsonb_path_query     
+-------------------------
+ "12:34:00+596523:14:07"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", 2147483648)');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  timezone argument of jsonpath item method .datetime() is out of integer range
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", -2147483647)');
+    jsonb_path_query     
+-------------------------
+ "12:34:00-596523:14:07"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", -2147483648)');
+ERROR:  invalid argument for SQL/JSON datetime function
+DETAIL:  timezone argument of jsonpath item method .datetime() is out of integer range
+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")');
+ jsonb_path_query 
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+ jsonb_path_query 
+------------------
+ "date"
+(1 row)
+
+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:  missing time-zone in input string for type timestamptz
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+00:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00:12")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+00:12"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "-00:12:34")');
+        jsonb_path_query        
+--------------------------------
+ "2017-03-10T12:34:00-00:12:34"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "UTC")');
+ERROR:  invalid input syntax for type timestamptz: "UTC"
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", 12 * 60)');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+00:12"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", -(5 * 3600 + 12 * 60 + 34))');
+        jsonb_path_query        
+--------------------------------
+ "2017-03-10T12:34:00-05:12:34"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", $tz)',
+                        jsonb_build_object('tz', extract(timezone from now())));
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+00: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"', '$.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:  missing time-zone in input string for type timetz
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+00")');
+ jsonb_path_query 
+------------------
+ "12:34:00+00: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"', '$.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:  missing time-zone in input string for type timestamptz
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+10")');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+10:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", $tz)',
+                        jsonb_build_object('tz', extract(timezone from now())));
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+10: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"', '$.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:  missing time-zone in input string for type timetz
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+10")');
+ jsonb_path_query 
+------------------
+ "12:34:00+10: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"', '$.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"))');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10"
+ "2017-03-10T00:00:00"
+(2 rows)
+
+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"))');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10"
+ "2017-03-11"
+ "2017-03-10T00:00:00"
+ "2017-03-10T12:34:56"
+(4 rows)
+
+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"))');
+ jsonb_path_query 
+------------------
+ "2017-03-09"
+(1 row)
+
+-- 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"))');
+ jsonb_path_query 
+------------------
+ "12:35:00"
+(1 row)
+
+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"))');
+ jsonb_path_query 
+------------------
+ "12:35:00"
+ "12:36:00"
+(2 rows)
+
+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"))');
+ jsonb_path_query 
+------------------
+ "12:34:00"
+(1 row)
+
+-- 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"))');
+ jsonb_path_query 
+------------------
+ "12:35:00+01:00"
+(1 row)
+
+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"))');
+ jsonb_path_query 
+------------------
+ "12:35:00+01:00"
+ "12:36:00+01:00"
+ "12:35:00-02:00"
+(3 rows)
+
+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"))');
+ jsonb_path_query 
+------------------
+ "12:34:00+01:00"
+ "12:35:00+02:00"
+(2 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"))');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:35:00"
+(1 row)
+
+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"))');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T12:36:00"
+ "2017-03-11"
+(3 rows)
+
+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"))');
+   jsonb_path_query    
+-----------------------
+ "2017-03-10T12:34:00"
+ "2017-03-10"
+(2 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"))');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+(1 row)
+
+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"))');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+(3 rows)
+
+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"))');
+      jsonb_path_query       
+-----------------------------
+ "2017-03-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+(2 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 baaf9e3..7b94746 100644
--- a/src/test/regress/expected/jsonpath.out
+++ b/src/test/regress/expected/jsonpath.out
@@ -389,6 +389,24 @@ 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 '$.datetime("datetime template", "default timezone")'::jsonpath;
+                      jsonpath                       
+-----------------------------------------------------
+ $.datetime("datetime template", "default timezone")
+(1 row)
+
 select '$ ? (@ starts with "abc")'::jsonpath;
         jsonpath         
 -------------------------
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index 4a2fabd..cb3dd19 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 8a4c719..cdd3c14 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 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/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
index bb5f97b..7245250 100644
--- a/src/test/regress/sql/jsonb_jsonpath.sql
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -301,6 +301,154 @@ 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('"12:34"', '$.datetime("aaa", 1)');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", 1)');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", 10000000000000)');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", 10000000000000)');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", 2147483647)');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", 2147483648)');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", -2147483647)');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", -2147483648)');
+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"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00:12")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "-00:12:34")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "UTC")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", 12 * 60)');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", -(5 * 3600 + 12 * 60 + 34))');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", $tz)',
+                        jsonb_build_object('tz', extract(timezone from now())));
+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"', '$.datetime("HH24:MI TZH", "+00")');
+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"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+10")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", $tz)',
+                        jsonb_build_object('tz', extract(timezone from now())));
+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"', '$.datetime("HH24:MI TZH", "+10")');
+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"))');
+
+-- 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"))');
+
+-- 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"))');
+
+-- 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"))');
+
+-- 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"))');
+
+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 e5f3391..288da17 100644
--- a/src/test/regress/sql/jsonpath.sql
+++ b/src/test/regress/sql/jsonpath.sql
@@ -70,6 +70,9 @@ 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 '$.datetime("datetime template", "default timezone")'::jsonpath;
 
 select '$ ? (@ starts with "abc")'::jsonpath;
 select '$ ? (@ starts with $var)'::jsonpath;
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b7957cb..fea4255 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 c3bd46c..588c3e0 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";
-- 
2.7.4

