aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/adt/formatting.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/utils/adt/formatting.c')
-rw-r--r--src/backend/utils/adt/formatting.c167
1 files changed, 114 insertions, 53 deletions
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 83e1f1265ce..829aaa8d0e7 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);