Support TZ and OF format codes in to_timestamp().

Formerly, these were only supported in to_char(), but there seems
little reason for that restriction.  We should at least have enough
support to permit round-tripping the output of to_char().

In that spirit, TZ accepts either zone abbreviations or numeric
(HH or HH:MM) offsets, which are the cases that to_char() can output.
In an ideal world we'd make it take full zone names too, but
that seems like it'd introduce an unreasonable amount of ambiguity,
since the rules for POSIX-spec zone names are so lax.

OF is a subset of this, accepting only HH or HH:MM.

One small benefit of this improvement is that we can simplify
jsonpath's executeDateTimeMethod function, which no longer needs
to consider the HH and HH:MM cases separately.  Moreover, letting
it accept zone abbreviations means it will accept "Z" to mean UTC,
which is emitted by JSON.stringify() for example.

Patch by me, reviewed by Aleksander Alekseev and Daniel Gustafsson

Discussion: https://postgr.es/m/1681086.1686673242@sss.pgh.pa.us
This commit is contained in:
Tom Lane 2024-01-25 17:47:08 -05:00
parent 06a66d87db
commit 8ba6fdf905
9 changed files with 318 additions and 74 deletions

View File

@ -8131,13 +8131,11 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
</row>
<row>
<entry><literal>TZ</literal></entry>
<entry>upper case time-zone abbreviation
(only supported in <function>to_char</function>)</entry>
<entry>upper case time-zone abbreviation</entry>
</row>
<row>
<entry><literal>tz</literal></entry>
<entry>lower case time-zone abbreviation
(only supported in <function>to_char</function>)</entry>
<entry>lower case time-zone abbreviation</entry>
</row>
<row>
<entry><literal>TZH</literal></entry>
@ -8149,8 +8147,8 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
</row>
<row>
<entry><literal>OF</literal></entry>
<entry>time-zone offset from UTC
(only supported in <function>to_char</function>)</entry>
<entry>time-zone offset from UTC (<replaceable>HH</replaceable>
or <replaceable>HH</replaceable><literal>:</literal><replaceable>MM</replaceable>)</entry>
</row>
</tbody>
</tgroup>

View File

@ -3246,6 +3246,82 @@ DecodeTimezoneNameToTz(const char *tzname)
return result;
}
/* DecodeTimezoneAbbrevPrefix()
* Interpret prefix of string as a timezone abbreviation, if possible.
*
* This has roughly the same functionality as DecodeTimezoneAbbrev(),
* but the API is adapted to the needs of formatting.c. Notably,
* we will match the longest possible prefix of the given string
* rather than insisting on a complete match, and downcasing is applied
* here rather than in the caller.
*
* Returns the length of the timezone abbreviation, or -1 if not recognized.
* On success, sets *offset to the GMT offset for the abbreviation if it
* is a fixed-offset abbreviation, or sets *tz to the pg_tz struct for
* a dynamic abbreviation.
*/
int
DecodeTimezoneAbbrevPrefix(const char *str, int *offset, pg_tz **tz)
{
char lowtoken[TOKMAXLEN + 1];
int len;
*offset = 0; /* avoid uninitialized vars on failure */
*tz = NULL;
if (!zoneabbrevtbl)
return -1; /* no abbrevs known, so fail immediately */
/* Downcase as much of the string as we could need */
for (len = 0; len < TOKMAXLEN; len++)
{
if (*str == '\0' || !isalpha((unsigned char) *str))
break;
lowtoken[len] = pg_tolower((unsigned char) *str++);
}
lowtoken[len] = '\0';
/*
* We could avoid doing repeated binary searches if we cared to duplicate
* datebsearch here, but it's not clear that such an optimization would be
* worth the trouble. In common cases there's probably not anything after
* the zone abbrev anyway. So just search with successively truncated
* strings.
*/
while (len > 0)
{
const datetkn *tp = datebsearch(lowtoken, zoneabbrevtbl->abbrevs,
zoneabbrevtbl->numabbrevs);
if (tp != NULL)
{
if (tp->type == DYNTZ)
{
DateTimeErrorExtra extra;
pg_tz *tzp = FetchDynamicTimeZone(zoneabbrevtbl, tp,
&extra);
if (tzp != NULL)
{
/* Caller must resolve the abbrev's current meaning */
*tz = tzp;
return len;
}
}
else
{
/* Fixed-offset zone abbrev, so it's easy */
*offset = tp->value;
return len;
}
}
lowtoken[--len] = '\0';
}
/* Did not find a match */
return -1;
}
/* ClearPgItmIn
*

View File

@ -418,14 +418,24 @@ typedef struct
us,
yysz, /* is it YY or YYYY ? */
clock, /* 12 or 24 hour clock? */
tzsign, /* +1, -1 or 0 if timezone info is absent */
tzsign, /* +1, -1, or 0 if no TZH/TZM fields */
tzh,
tzm,
ff; /* fractional precision */
bool has_tz; /* was there a TZ field? */
int gmtoffset; /* GMT offset of fixed-offset zone abbrev */
pg_tz *tzp; /* pg_tz for dynamic abbrev */
char *abbrev; /* dynamic abbrev */
} TmFromChar;
#define ZERO_tmfc(_X) memset(_X, 0, sizeof(TmFromChar))
struct fmt_tz /* do_to_timestamp's timezone info output */
{
bool has_tz; /* was there any TZ/TZH/TZM field? */
int gmtoffset; /* GMT offset in seconds */
};
/* ----------
* Debug
* ----------
@ -1058,8 +1068,8 @@ static bool from_char_seq_search(int *dest, const char **src,
char **localized_array, Oid collid,
FormatNode *node, Node *escontext);
static bool do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std,
struct pg_tm *tm, fsec_t *fsec, int *fprec,
uint32 *flags, Node *escontext);
struct pg_tm *tm, fsec_t *fsec, struct fmt_tz *tz,
int *fprec, uint32 *flags, Node *escontext);
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);
@ -3444,7 +3454,7 @@ DCH_from_char(FormatNode *node, const char *in, TmFromChar *out,
case DCH_FF5:
case DCH_FF6:
out->ff = n->key->id - DCH_FF1 + 1;
/* fall through */
/* FALLTHROUGH */
case DCH_US: /* microsecond */
len = from_char_parse_int_len(&out->us, &s,
n->key->id == DCH_US ? 6 :
@ -3467,11 +3477,63 @@ DCH_from_char(FormatNode *node, const char *in, TmFromChar *out,
break;
case DCH_tz:
case DCH_TZ:
{
int tzlen;
tzlen = DecodeTimezoneAbbrevPrefix(s,
&out->gmtoffset,
&out->tzp);
if (tzlen > 0)
{
out->has_tz = true;
/* we only need the zone abbrev for DYNTZ case */
if (out->tzp)
out->abbrev = pnstrdup(s, tzlen);
out->tzsign = 0; /* drop any earlier TZH/TZM info */
s += tzlen;
break;
}
else if (isalpha((unsigned char) *s))
{
/*
* It doesn't match any abbreviation, but it starts
* with a letter. OF format certainly won't succeed;
* assume it's a misspelled abbreviation and complain
* accordingly.
*/
ereturn(escontext,,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid value \"%s\" for \"%s\"",
s, n->key->name),
errdetail("Time zone abbreviation is not recognized.")));
}
/* otherwise parse it like OF */
}
/* FALLTHROUGH */
case DCH_OF:
ereturn(escontext,,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("formatting field \"%s\" is only supported in to_char",
n->key->name)));
/* OF is equivalent to TZH or TZH:TZM */
/* see TZH comments below */
if (*s == '+' || *s == '-' || *s == ' ')
{
out->tzsign = *s == '-' ? -1 : +1;
s++;
}
else
{
if (extra_skip > 0 && *(s - 1) == '-')
out->tzsign = -1;
else
out->tzsign = +1;
}
if (from_char_parse_int_len(&out->tzh, &s, 2, n, escontext) < 0)
return;
if (*s == ':')
{
s++;
if (from_char_parse_int_len(&out->tzm, &s, 2, n,
escontext) < 0)
return;
}
break;
case DCH_TZH:
@ -4167,22 +4229,16 @@ to_timestamp(PG_FUNCTION_ARGS)
Timestamp result;
int tz;
struct pg_tm tm;
struct fmt_tz ftz;
fsec_t fsec;
int fprec;
do_to_timestamp(date_txt, fmt, collid, false,
&tm, &fsec, &fprec, NULL, NULL);
&tm, &fsec, &ftz, &fprec, NULL, NULL);
/* Use the specified time zone, if any. */
if (tm.tm_zone)
{
DateTimeErrorExtra extra;
int dterr = DecodeTimezone(tm.tm_zone, &tz);
if (dterr)
DateTimeParseError(dterr, &extra, text_to_cstring(date_txt),
"timestamptz", NULL);
}
if (ftz.has_tz)
tz = ftz.gmtoffset;
else
tz = DetermineTimeZoneOffset(&tm, session_timezone);
@ -4211,10 +4267,11 @@ to_date(PG_FUNCTION_ARGS)
Oid collid = PG_GET_COLLATION();
DateADT result;
struct pg_tm tm;
struct fmt_tz ftz;
fsec_t fsec;
do_to_timestamp(date_txt, fmt, collid, false,
&tm, &fsec, NULL, NULL, NULL);
&tm, &fsec, &ftz, NULL, NULL, NULL);
/* Prevent overflow in Julian-day routines */
if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@ -4256,12 +4313,13 @@ parse_datetime(text *date_txt, text *fmt, Oid collid, bool strict,
Node *escontext)
{
struct pg_tm tm;
struct fmt_tz ftz;
fsec_t fsec;
int fprec;
uint32 flags;
if (!do_to_timestamp(date_txt, fmt, collid, strict,
&tm, &fsec, &fprec, &flags, escontext))
&tm, &fsec, &ftz, &fprec, &flags, escontext))
return (Datum) 0;
*typmod = fprec ? fprec : -1; /* fractional part precision */
@ -4274,18 +4332,9 @@ parse_datetime(text *date_txt, text *fmt, Oid collid, bool strict,
{
TimestampTz result;
if (tm.tm_zone)
if (ftz.has_tz)
{
DateTimeErrorExtra extra;
int dterr = DecodeTimezone(tm.tm_zone, tz);
if (dterr)
{
DateTimeParseError(dterr, &extra,
text_to_cstring(date_txt),
"timestamptz", escontext);
return (Datum) 0;
}
*tz = ftz.gmtoffset;
}
else
{
@ -4366,18 +4415,9 @@ parse_datetime(text *date_txt, text *fmt, Oid collid, bool strict,
{
TimeTzADT *result = palloc(sizeof(TimeTzADT));
if (tm.tm_zone)
if (ftz.has_tz)
{
DateTimeErrorExtra extra;
int dterr = DecodeTimezone(tm.tm_zone, tz);
if (dterr)
{
DateTimeParseError(dterr, &extra,
text_to_cstring(date_txt),
"timetz", escontext);
return (Datum) 0;
}
*tz = ftz.gmtoffset;
}
else
{
@ -4430,7 +4470,7 @@ parse_datetime(text *date_txt, text *fmt, Oid collid, bool strict,
* 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,
* fractional seconds, and fractional precision.
* fractional seconds, struct fmt_tz, and fractional precision.
*
* 'collid' identifies the collation to use, if needed.
* 'std' specifies standard parsing mode.
@ -4447,12 +4487,12 @@ parse_datetime(text *date_txt, text *fmt, Oid collid, bool strict,
* 'date_txt'.
*
* The TmFromChar is then analysed and converted into the final results in
* struct 'tm', 'fsec', and 'fprec'.
* struct 'tm', 'fsec', struct 'tz', and 'fprec'.
*/
static bool
do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std,
struct pg_tm *tm, fsec_t *fsec, int *fprec,
uint32 *flags, Node *escontext)
struct pg_tm *tm, fsec_t *fsec, struct fmt_tz *tz,
int *fprec, uint32 *flags, Node *escontext)
{
FormatNode *format = NULL;
TmFromChar tmfc;
@ -4469,6 +4509,7 @@ do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std,
ZERO_tmfc(&tmfc);
ZERO_tm(tm);
*fsec = 0;
tz->has_tz = false;
if (fprec)
*fprec = 0;
if (flags)
@ -4744,11 +4785,14 @@ do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std,
goto fail;
}
/* Save parsed time-zone into tm->tm_zone if it was specified */
/*
* If timezone info was present, reduce it to a GMT offset. (We cannot do
* this until we've filled all of the tm struct, since the zone's offset
* might be time-varying.)
*/
if (tmfc.tzsign)
{
char *tz;
/* TZH and/or TZM fields */
if (tmfc.tzh < 0 || tmfc.tzh > MAX_TZDISP_HOUR ||
tmfc.tzm < 0 || tmfc.tzm >= MINS_PER_HOUR)
{
@ -4757,10 +4801,27 @@ do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std,
goto fail;
}
tz = psprintf("%c%02d:%02d",
tmfc.tzsign > 0 ? '+' : '-', tmfc.tzh, tmfc.tzm);
tm->tm_zone = tz;
tz->has_tz = true;
tz->gmtoffset = (tmfc.tzh * MINS_PER_HOUR + tmfc.tzm) * SECS_PER_MINUTE;
/* note we are flipping the sign convention here */
if (tmfc.tzsign > 0)
tz->gmtoffset = -tz->gmtoffset;
}
else if (tmfc.has_tz)
{
/* TZ field */
tz->has_tz = true;
if (tmfc.tzp == NULL)
{
/* fixed-offset abbreviation; flip the sign convention */
tz->gmtoffset = -tmfc.gmtoffset;
}
else
{
/* dynamic-offset abbreviation, resolve using specified time */
tz->gmtoffset = DetermineTimeZoneAbbrevOffset(tm, tmfc.abbrev,
tmfc.tzp);
}
}
DEBUG_TM(tm);

View File

@ -2296,20 +2296,14 @@ executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
static const char *fmt_str[] =
{
"yyyy-mm-dd", /* date */
"HH24:MI:SS.USTZH:TZM", /* timetz */
"HH24:MI:SS.USTZH",
"HH24:MI:SSTZH:TZM",
"HH24:MI:SSTZH",
"HH24:MI:SS.USTZ", /* timetz */
"HH24:MI:SSTZ",
"HH24:MI:SS.US", /* time without tz */
"HH24:MI:SS",
"yyyy-mm-dd HH24:MI:SS.USTZH:TZM", /* timestamptz */
"yyyy-mm-dd HH24:MI:SS.USTZH",
"yyyy-mm-dd HH24:MI:SSTZH:TZM",
"yyyy-mm-dd HH24:MI:SSTZH",
"yyyy-mm-dd\"T\"HH24:MI:SS.USTZH:TZM",
"yyyy-mm-dd\"T\"HH24:MI:SS.USTZH",
"yyyy-mm-dd\"T\"HH24:MI:SSTZH:TZM",
"yyyy-mm-dd\"T\"HH24:MI:SSTZH",
"yyyy-mm-dd HH24:MI:SS.USTZ", /* timestamptz */
"yyyy-mm-dd HH24:MI:SSTZ",
"yyyy-mm-dd\"T\"HH24:MI:SS.USTZ",
"yyyy-mm-dd\"T\"HH24:MI:SSTZ",
"yyyy-mm-dd HH24:MI:SS.US", /* timestamp without tz */
"yyyy-mm-dd HH24:MI:SS",
"yyyy-mm-dd\"T\"HH24:MI:SS.US",

View File

@ -348,6 +348,9 @@ extern int DecodeUnits(int field, const char *lowtoken, int *val);
extern int DecodeTimezoneName(const char *tzname, int *offset, pg_tz **tz);
extern pg_tz *DecodeTimezoneNameToTz(const char *tzname);
extern int DecodeTimezoneAbbrevPrefix(const char *str,
int *offset, pg_tz **tz);
extern int j2day(int date);
extern struct Node *TemporalSimplify(int32 max_precis, struct Node *node);

View File

@ -3140,8 +3140,66 @@ 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 to_timestamp('2011-12-18 11:38 PST', 'YYYY-MM-DD HH12:MI TZ'); -- NYI
ERROR: formatting field "TZ" is only supported in to_char
SELECT to_timestamp('2011-12-18 11:38 EST', 'YYYY-MM-DD HH12:MI TZ');
to_timestamp
------------------------------
Sun Dec 18 08:38:00 2011 PST
(1 row)
SELECT to_timestamp('2011-12-18 11:38 -05', 'YYYY-MM-DD HH12:MI TZ');
to_timestamp
------------------------------
Sun Dec 18 08:38:00 2011 PST
(1 row)
SELECT to_timestamp('2011-12-18 11:38 +01:30', 'YYYY-MM-DD HH12:MI TZ');
to_timestamp
------------------------------
Sun Dec 18 02:08:00 2011 PST
(1 row)
SELECT to_timestamp('2011-12-18 11:38 MSK', 'YYYY-MM-DD HH12:MI TZ'); -- dyntz
to_timestamp
------------------------------
Sat Dec 17 23:38:00 2011 PST
(1 row)
SELECT to_timestamp('2011-12-18 11:38ESTFOO24', 'YYYY-MM-DD HH12:MITZFOOSS');
to_timestamp
------------------------------
Sun Dec 18 08:38:24 2011 PST
(1 row)
SELECT to_timestamp('2011-12-18 11:38-05FOO24', 'YYYY-MM-DD HH12:MITZFOOSS');
to_timestamp
------------------------------
Sun Dec 18 08:38:24 2011 PST
(1 row)
SELECT to_timestamp('2011-12-18 11:38 JUNK', 'YYYY-MM-DD HH12:MI TZ'); -- error
ERROR: invalid value "JUNK" for "TZ"
DETAIL: Time zone abbreviation is not recognized.
SELECT to_timestamp('2011-12-18 11:38 ...', 'YYYY-MM-DD HH12:MI TZ'); -- error
ERROR: invalid value ".." for "TZ"
DETAIL: Value must be an integer.
SELECT to_timestamp('2011-12-18 11:38 -05', 'YYYY-MM-DD HH12:MI OF');
to_timestamp
------------------------------
Sun Dec 18 08:38:00 2011 PST
(1 row)
SELECT to_timestamp('2011-12-18 11:38 +01:30', 'YYYY-MM-DD HH12:MI OF');
to_timestamp
------------------------------
Sun Dec 18 02:08:00 2011 PST
(1 row)
SELECT to_timestamp('2011-12-18 11:38 +xyz', 'YYYY-MM-DD HH12:MI OF'); -- error
ERROR: invalid value "xy" for "OF"
DETAIL: Value must be an integer.
SELECT to_timestamp('2011-12-18 11:38 +01:xyz', 'YYYY-MM-DD HH12:MI OF'); -- error
ERROR: invalid value "xy" for "OF"
DETAIL: Value must be an integer.
SELECT to_timestamp('2018-11-02 12:34:56.025', 'YYYY-MM-DD HH24:MI:SS.MS');
to_timestamp
----------------------------------
@ -3557,6 +3615,19 @@ SELECT to_date('0000-02-01','YYYY-MM-DD'); -- allowed, though it shouldn't be
02-01-0001 BC
(1 row)
-- to_char's TZ format code produces zone abbrev if known
SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD HH:MI:SS TZ');
to_char
-------------------------
2012-12-12 12:00:00 PST
(1 row)
SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD HH:MI:SS tz');
to_char
-------------------------
2012-12-12 12:00:00 pst
(1 row)
--
-- Check behavior with SQL-style fixed-GMT-offset time zone (cf bug #8572)
--
@ -3598,4 +3669,11 @@ SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD SSSSS');
2012-12-12 43200
(1 row)
SET TIME ZONE '+2';
SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD HH:MI:SS TZ');
to_char
-------------------------
2012-12-12 12:00:00 +02
(1 row)
RESET TIME ZONE;

View File

@ -3228,6 +3228,18 @@ select jsonb_path_query('"2017-03-10T12:34:56.789+3:10"', '$.datetime()');
select jsonb_path_query('"2017-03-10t12:34:56.789+3:10"', '$.datetime()');
ERROR: datetime format is not recognized: "2017-03-10t12:34:56.789+3:10"
HINT: Use a datetime template argument to specify the input data format.
select jsonb_path_query('"2017-03-10T12:34:56.789EST"', '$.datetime()');
jsonb_path_query
---------------------------------
"2017-03-10T12:34:56.789-05:00"
(1 row)
select jsonb_path_query('"2017-03-10T12:34:56.789Z"', '$.datetime()');
jsonb_path_query
---------------------------------
"2017-03-10T12:34:56.789+00:00"
(1 row)
select jsonb_path_query('"12:34:56"', '$.datetime().type()');
jsonb_path_query
--------------------------

View File

@ -501,7 +501,19 @@ 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 to_timestamp('2011-12-18 11:38 PST', 'YYYY-MM-DD HH12:MI TZ'); -- NYI
SELECT to_timestamp('2011-12-18 11:38 EST', 'YYYY-MM-DD HH12:MI TZ');
SELECT to_timestamp('2011-12-18 11:38 -05', 'YYYY-MM-DD HH12:MI TZ');
SELECT to_timestamp('2011-12-18 11:38 +01:30', 'YYYY-MM-DD HH12:MI TZ');
SELECT to_timestamp('2011-12-18 11:38 MSK', 'YYYY-MM-DD HH12:MI TZ'); -- dyntz
SELECT to_timestamp('2011-12-18 11:38ESTFOO24', 'YYYY-MM-DD HH12:MITZFOOSS');
SELECT to_timestamp('2011-12-18 11:38-05FOO24', 'YYYY-MM-DD HH12:MITZFOOSS');
SELECT to_timestamp('2011-12-18 11:38 JUNK', 'YYYY-MM-DD HH12:MI TZ'); -- error
SELECT to_timestamp('2011-12-18 11:38 ...', 'YYYY-MM-DD HH12:MI TZ'); -- error
SELECT to_timestamp('2011-12-18 11:38 -05', 'YYYY-MM-DD HH12:MI OF');
SELECT to_timestamp('2011-12-18 11:38 +01:30', 'YYYY-MM-DD HH12:MI OF');
SELECT to_timestamp('2011-12-18 11:38 +xyz', 'YYYY-MM-DD HH12:MI OF'); -- error
SELECT to_timestamp('2011-12-18 11:38 +01:xyz', 'YYYY-MM-DD HH12:MI OF'); -- error
SELECT to_timestamp('2018-11-02 12:34:56.025', 'YYYY-MM-DD HH24:MI:SS.MS');
@ -616,6 +628,10 @@ SELECT to_date('2016 366', 'YYYY DDD'); -- ok
SELECT to_date('2016 367', 'YYYY DDD');
SELECT to_date('0000-02-01','YYYY-MM-DD'); -- allowed, though it shouldn't be
-- to_char's TZ format code produces zone abbrev if known
SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD HH:MI:SS TZ');
SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD HH:MI:SS tz');
--
-- Check behavior with SQL-style fixed-GMT-offset time zone (cf bug #8572)
--
@ -632,4 +648,8 @@ SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD HH:MI:SS TZ');
SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD SSSS');
SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD SSSSS');
SET TIME ZONE '+2';
SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD HH:MI:SS TZ');
RESET TIME ZONE;

View File

@ -755,6 +755,8 @@ select jsonb_path_query('"2017-03-10t12:34:56+3:10"', '$.datetime()');
select jsonb_path_query('"2017-03-10 12:34:56.789+3:10"', '$.datetime()');
select jsonb_path_query('"2017-03-10T12:34:56.789+3:10"', '$.datetime()');
select jsonb_path_query('"2017-03-10t12:34:56.789+3:10"', '$.datetime()');
select jsonb_path_query('"2017-03-10T12:34:56.789EST"', '$.datetime()');
select jsonb_path_query('"2017-03-10T12:34:56.789Z"', '$.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()');