From 8ba6fdf905d0f5aef70ced4504c6ad297bfe08ea Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Thu, 25 Jan 2024 17:47:08 -0500 Subject: [PATCH] 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 --- doc/src/sgml/func.sgml | 10 +- src/backend/utils/adt/datetime.c | 76 +++++++++ src/backend/utils/adt/formatting.c | 167 +++++++++++++------ src/backend/utils/adt/jsonpath_exec.c | 18 +- src/include/utils/datetime.h | 3 + src/test/regress/expected/horology.out | 82 ++++++++- src/test/regress/expected/jsonb_jsonpath.out | 12 ++ src/test/regress/sql/horology.sql | 22 ++- src/test/regress/sql/jsonb_jsonpath.sql | 2 + 9 files changed, 318 insertions(+), 74 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 4f7195c508249..6788ba8ef4a54 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -8131,13 +8131,11 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}'); TZ - upper case time-zone abbreviation - (only supported in to_char) + upper case time-zone abbreviation tz - lower case time-zone abbreviation - (only supported in to_char) + lower case time-zone abbreviation TZH @@ -8149,8 +8147,8 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}'); OF - time-zone offset from UTC - (only supported in to_char) + time-zone offset from UTC (HH + or HH:MM) diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c index 17b0248bf78a1..cccabb0c2ad21 100644 --- a/src/backend/utils/adt/datetime.c +++ b/src/backend/utils/adt/datetime.c @@ -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 * diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c index 83e1f1265ce51..829aaa8d0e7fc 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -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); diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 02a700292bbc0..22f598cd3592c 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -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", diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h index 460c75cfdd085..e4ac2b8e7f6c9 100644 --- a/src/include/utils/datetime.h +++ b/src/include/utils/datetime.h @@ -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); diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out index cfb4b205e4d4f..0bdb76695a19a 100644 --- a/src/test/regress/expected/horology.out +++ b/src/test/regress/expected/horology.out @@ -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; diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index e758d729f4351..eea2af30c8b2c 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -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 -------------------------- diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql index 252bce4b1ce18..3b9573e954c84 100644 --- a/src/test/regress/sql/horology.sql +++ b/src/test/regress/sql/horology.sql @@ -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; diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index 418eeac5ec716..6406a2ea3b0dc 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -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()');