fsec_t fsec;
int tz = 0;
struct pg_tm date;
+ DateTimeErrorExtra extra;
/*
* Default format: postgresql-YYYY-MM-DD_HHMMSS.log
if (ParseDateTime(timestampbuf, lowstr, MAXDATELEN, field, ftype, MAXDATEFIELDS, &nf))
continue;
- if (DecodeDateTime(field, ftype, nf, &dtype, &date, &fsec, &tz))
+ if (DecodeDateTime(field, ftype, nf,
+ &dtype, &date, &fsec, &tz, &extra))
continue;
/* Seems the timestamp is OK; prepare and return tuple */
char *field[MAXDATEFIELDS];
int ftype[MAXDATEFIELDS];
char workbuf[MAXDATELEN + MAXDATEFIELDS];
+ DateTimeErrorExtra dtextra;
TimestampTz timestamp;
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
- dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz);
+ dterr = DecodeDateTime(field, ftype, nf,
+ &dtype, tm, &fsec, &tz, &dtextra);
if (dterr != 0)
return false;
if (dtype != DTK_DATE)
char *field[MAXDATEFIELDS];
int ftype[MAXDATEFIELDS];
char workbuf[MAXDATELEN + 1];
+ DateTimeErrorExtra extra;
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
- dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tzp);
+ dterr = DecodeDateTime(field, ftype, nf,
+ &dtype, tm, &fsec, &tzp, &extra);
if (dterr != 0)
- DateTimeParseError(dterr, str, "date");
+ DateTimeParseError(dterr, &extra, str, "date");
switch (dtype)
{
PG_RETURN_DATEADT(date);
default:
- DateTimeParseError(DTERR_BAD_FORMAT, str, "date");
+ DateTimeParseError(DTERR_BAD_FORMAT, &extra, str, "date");
break;
}
char *field[MAXDATEFIELDS];
int dtype;
int ftype[MAXDATEFIELDS];
+ DateTimeErrorExtra extra;
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
- dterr = DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz);
+ dterr = DecodeTimeOnly(field, ftype, nf,
+ &dtype, tm, &fsec, &tz, &extra);
if (dterr != 0)
- DateTimeParseError(dterr, str, "time");
+ DateTimeParseError(dterr, &extra, str, "time");
tm2time(tm, fsec, &result);
AdjustTimeForTypmod(&result, typmod);
char *field[MAXDATEFIELDS];
int dtype;
int ftype[MAXDATEFIELDS];
+ DateTimeErrorExtra extra;
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
- dterr = DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz);
+ dterr = DecodeTimeOnly(field, ftype, nf,
+ &dtype, tm, &fsec, &tz, &extra);
if (dterr != 0)
- DateTimeParseError(dterr, str, "time with time zone");
+ DateTimeParseError(dterr, &extra, str, "time with time zone");
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
tm2timetz(tm, fsec, tz, result);
int tz;
char tzname[TZ_STRLEN_MAX + 1];
char *lowzone;
- int type,
+ int dterr,
+ type,
val;
pg_tz *tzp;
+ DateTimeErrorExtra extra;
/*
* Look up the requested timezone. First we look in the timezone
strlen(tzname),
false);
- type = DecodeTimezoneAbbrev(0, lowzone, &val, &tzp);
+ dterr = DecodeTimezoneAbbrev(0, lowzone, &type, &val, &tzp, &extra);
+ if (dterr)
+ DateTimeParseError(dterr, &extra, NULL, NULL);
if (type == TZ || type == DTZ)
{
static bool DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t,
const char *abbr, pg_tz *tzp,
int *offset, int *isdst);
-static pg_tz *FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp);
+static pg_tz *FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp,
+ DateTimeErrorExtra *extra);
const int day_tab[2][13] =
* Return 0 if full date, 1 if only time, and negative DTERR code if problems.
* (Currently, all callers treat 1 as an error return too.)
*
+ * Inputs are field[] and ftype[] arrays, of length nf.
+ * Other arguments are outputs.
+ *
* External format(s):
* "<weekday> <month>-<day>-<year> <hour>:<minute>:<second>"
* "Fri Feb-7-1997 15:23:27"
*/
int
DecodeDateTime(char **field, int *ftype, int nf,
- int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp)
+ int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
+ DateTimeErrorExtra *extra)
{
int fmask = 0,
tmask,
namedTz = pg_tzset(field[i]);
if (!namedTz)
{
- /*
- * We should return an error code instead of
- * ereport'ing directly, but then there is no way
- * to report the bad time zone name.
- */
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("time zone \"%s\" not recognized",
- field[i])));
+ extra->dtee_timezone = field[i];
+ return DTERR_BAD_TIMEZONE;
}
/* we'll apply the zone setting below */
tmask = DTK_M(TZ);
case DTK_STRING:
case DTK_SPECIAL:
/* timezone abbrevs take precedence over built-in tokens */
- type = DecodeTimezoneAbbrev(i, field[i], &val, &valtz);
+ dterr = DecodeTimezoneAbbrev(i, field[i],
+ &type, &val, &valtz, extra);
+ if (dterr)
+ return dterr;
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(i, field[i], &val);
if (type == IGNORE_DTF)
* Interpret parsed string as time fields only.
* Returns 0 if successful, DTERR code if bogus input detected.
*
+ * Inputs are field[] and ftype[] arrays, of length nf.
+ * Other arguments are outputs.
+ *
* Note that support for time zone is here for
* SQL TIME WITH TIME ZONE, but it reveals
* bogosity with SQL date/time standards, since
*/
int
DecodeTimeOnly(char **field, int *ftype, int nf,
- int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp)
+ int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
+ DateTimeErrorExtra *extra)
{
int fmask = 0,
tmask,
namedTz = pg_tzset(field[i]);
if (!namedTz)
{
- /*
- * We should return an error code instead of
- * ereport'ing directly, but then there is no way
- * to report the bad time zone name.
- */
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("time zone \"%s\" not recognized",
- field[i])));
+ extra->dtee_timezone = field[i];
+ return DTERR_BAD_TIMEZONE;
}
/* we'll apply the zone setting below */
ftype[i] = DTK_TZ;
case DTK_STRING:
case DTK_SPECIAL:
/* timezone abbrevs take precedence over built-in tokens */
- type = DecodeTimezoneAbbrev(i, field[i], &val, &valtz);
+ dterr = DecodeTimezoneAbbrev(i, field[i],
+ &type, &val, &valtz, extra);
+ if (dterr)
+ return dterr;
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(i, field[i], &val);
if (type == IGNORE_DTF)
/* DecodeTimezoneAbbrev()
* Interpret string as a timezone abbreviation, if possible.
*
- * Returns an abbreviation type (TZ, DTZ, or DYNTZ), or UNKNOWN_FIELD if
+ * Sets *ftype to an abbreviation type (TZ, DTZ, or DYNTZ), or UNKNOWN_FIELD if
* string is not any known abbreviation. On success, set *offset and *tz to
* represent the UTC offset (for TZ or DTZ) or underlying zone (for DYNTZ).
* Note that full timezone names (such as America/New_York) are not handled
* here, mostly for historical reasons.
*
+ * The function result is 0 or a DTERR code; in the latter case, *extra
+ * is filled as needed. Note that unknown-abbreviation is not considered
+ * an error case. Also note that many callers assume that the DTERR code
+ * is one that DateTimeParseError does not require "str" or "datatype"
+ * strings for.
+ *
* Given string must be lowercased already.
*
* Implement a cache lookup since it is likely that dates
*/
int
DecodeTimezoneAbbrev(int field, const char *lowtoken,
- int *offset, pg_tz **tz)
+ int *ftype, int *offset, pg_tz **tz,
+ DateTimeErrorExtra *extra)
{
- int type;
const datetkn *tp;
tp = abbrevcache[field];
}
if (tp == NULL)
{
- type = UNKNOWN_FIELD;
+ *ftype = UNKNOWN_FIELD;
*offset = 0;
*tz = NULL;
}
else
{
abbrevcache[field] = tp;
- type = tp->type;
- if (type == DYNTZ)
+ *ftype = tp->type;
+ if (tp->type == DYNTZ)
{
*offset = 0;
- *tz = FetchDynamicTimeZone(zoneabbrevtbl, tp);
+ *tz = FetchDynamicTimeZone(zoneabbrevtbl, tp, extra);
+ if (*tz == NULL)
+ return DTERR_BAD_ZONE_ABBREV;
}
else
{
}
}
- return type;
+ return 0;
}
/*
* Report an error detected by one of the datetime input processing routines.
*
- * dterr is the error code, str is the original input string, datatype is
- * the name of the datatype we were trying to accept.
+ * dterr is the error code, and *extra contains any auxiliary info we need
+ * for the error report. extra can be NULL if not needed for the particular
+ * dterr value.
+ *
+ * str is the original input string, and datatype is the name of the datatype
+ * we were trying to accept. (For some DTERR codes, these are not used and
+ * can be NULL.)
*
* Note: it might seem useless to distinguish DTERR_INTERVAL_OVERFLOW and
* DTERR_TZDISP_OVERFLOW from DTERR_FIELD_OVERFLOW, but SQL99 mandates three
* separate SQLSTATE codes, so ...
*/
void
-DateTimeParseError(int dterr, const char *str, const char *datatype)
+DateTimeParseError(int dterr, DateTimeErrorExtra *extra,
+ const char *str, const char *datatype)
{
switch (dterr)
{
errmsg("time zone displacement out of range: \"%s\"",
str)));
break;
+ case DTERR_BAD_TIMEZONE:
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("time zone \"%s\" not recognized",
+ extra->dtee_timezone)));
+ break;
+ case DTERR_BAD_ZONE_ABBREV:
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("time zone \"%s\" not recognized",
+ extra->dtee_timezone),
+ errdetail("This time zone name appears in the configuration file for time zone abbreviation \"%s\".",
+ extra->dtee_abbrev)));
+ break;
case DTERR_BAD_FORMAT:
default:
ereport(ERROR,
/*
* Helper subroutine to locate pg_tz timezone for a dynamic abbreviation.
+ *
+ * On failure, returns NULL and fills *extra for a DTERR_BAD_ZONE_ABBREV error.
*/
static pg_tz *
-FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp)
+FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp,
+ DateTimeErrorExtra *extra)
{
DynamicZoneAbbrev *dtza;
if (dtza->tz == NULL)
{
dtza->tz = pg_tzset(dtza->zone);
-
- /*
- * Ideally we'd let the caller ereport instead of doing it here, but
- * then there is no way to report the bad time zone name.
- */
if (dtza->tz == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_CONFIG_FILE_ERROR),
- errmsg("time zone \"%s\" not recognized",
- dtza->zone),
- errdetail("This time zone name appears in the configuration file for time zone abbreviation \"%s\".",
- tp->token)));
+ {
+ /* Ooops, bogus zone name in config file entry */
+ extra->dtee_timezone = dtza->zone;
+ extra->dtee_abbrev = tp->token;
+ }
}
return dtza->tz;
}
{
/* Determine the current meaning of the abbrev */
pg_tz *tzp;
+ DateTimeErrorExtra extra;
TimestampTz now;
int isdst;
- tzp = FetchDynamicTimeZone(zoneabbrevtbl, tp);
+ tzp = FetchDynamicTimeZone(zoneabbrevtbl, tp, &extra);
+ if (tzp == NULL)
+ DateTimeParseError(DTERR_BAD_ZONE_ABBREV, &extra,
+ NULL, NULL);
now = GetCurrentTransactionStartTimestamp();
gmtoffset = -DetermineTimeZoneAbbrevOffsetTS(now,
tp->token,
/* Use the specified time zone, if any. */
if (tm.tm_zone)
{
+ DateTimeErrorExtra extra;
int dterr = DecodeTimezone(tm.tm_zone, &tz);
if (dterr)
- DateTimeParseError(dterr, text_to_cstring(date_txt), "timestamptz");
+ DateTimeParseError(dterr, &extra, text_to_cstring(date_txt),
+ "timestamptz");
}
else
tz = DetermineTimeZoneOffset(&tm, session_timezone);
if (tm.tm_zone)
{
+ DateTimeErrorExtra extra;
int dterr = DecodeTimezone(tm.tm_zone, tz);
if (dterr)
- DateTimeParseError(dterr, text_to_cstring(date_txt), "timestamptz");
+ DateTimeParseError(dterr, &extra,
+ text_to_cstring(date_txt),
+ "timestamptz");
}
else
{
if (tm.tm_zone)
{
+ DateTimeErrorExtra extra;
int dterr = DecodeTimezone(tm.tm_zone, tz);
if (dterr)
- RETURN_ERROR(DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz"));
+ RETURN_ERROR(DateTimeParseError(dterr, &extra,
+ text_to_cstring(date_txt),
+ "timetz"));
}
else
{
* said DTERR_MD_FIELD_OVERFLOW, because we don't want to print an
* irrelevant hint about datestyle.
*/
- RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp"));
+ RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL, date_str, "timestamp"));
}
}
tm->tm_sec < 0 || tm->tm_sec >= SECS_PER_MINUTE ||
*fsec < INT64CONST(0) || *fsec >= USECS_PER_SEC)
{
- RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp"));
+ RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL, date_str, "timestamp"));
}
/* Save parsed time-zone into tm->tm_zone if it was specified */
if (tmfc.tzh < 0 || tmfc.tzh > MAX_TZDISP_HOUR ||
tmfc.tzm < 0 || tmfc.tzm >= MINS_PER_HOUR)
{
- RETURN_ERROR(DateTimeParseError(DTERR_TZDISP_OVERFLOW, date_str, "timestamp"));
+ RETURN_ERROR(DateTimeParseError(DTERR_TZDISP_OVERFLOW, NULL, date_str, "timestamp"));
}
tz = psprintf("%c%02d:%02d",
char *field[MAXDATEFIELDS];
int ftype[MAXDATEFIELDS];
char workbuf[MAXDATELEN + MAXDATEFIELDS];
+ DateTimeErrorExtra extra;
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
- dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz);
+ dterr = DecodeDateTime(field, ftype, nf,
+ &dtype, tm, &fsec, &tz, &extra);
if (dterr != 0)
- DateTimeParseError(dterr, str, "timestamp");
+ DateTimeParseError(dterr, &extra, str, "timestamp");
switch (dtype)
{
char *field[MAXDATEFIELDS];
int ftype[MAXDATEFIELDS];
char workbuf[MAXDATELEN + MAXDATEFIELDS];
+ DateTimeErrorExtra extra;
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
- dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz);
+ dterr = DecodeDateTime(field, ftype, nf,
+ &dtype, tm, &fsec, &tz, &extra);
if (dterr != 0)
- DateTimeParseError(dterr, str, "timestamp with time zone");
+ DateTimeParseError(dterr, &extra, str, "timestamp with time zone");
switch (dtype)
{
parse_sane_timezone(struct pg_tm *tm, text *zone)
{
char tzname[TZ_STRLEN_MAX + 1];
- int rt;
+ int dterr;
int tz;
text_to_cstring_buffer(zone, tzname, sizeof(tzname));
"numeric time zone", tzname),
errhint("Numeric time zones must have \"-\" or \"+\" as first character.")));
- rt = DecodeTimezone(tzname, &tz);
- if (rt != 0)
+ dterr = DecodeTimezone(tzname, &tz);
+ if (dterr != 0)
{
char *lowzone;
int type,
val;
pg_tz *tzp;
+ DateTimeErrorExtra extra;
- if (rt == DTERR_TZDISP_OVERFLOW)
+ if (dterr == DTERR_TZDISP_OVERFLOW)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("numeric time zone \"%s\" out of range", tzname)));
- else if (rt != DTERR_BAD_FORMAT)
+ else if (dterr != DTERR_BAD_FORMAT)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("time zone \"%s\" not recognized", tzname)));
lowzone = downcase_truncate_identifier(tzname,
strlen(tzname),
false);
- type = DecodeTimezoneAbbrev(0, lowzone, &val, &tzp);
+ dterr = DecodeTimezoneAbbrev(0, lowzone, &type, &val, &tzp, &extra);
+ if (dterr)
+ DateTimeParseError(dterr, &extra, NULL, NULL);
if (type == TZ || type == DTZ)
{
char *field[MAXDATEFIELDS];
int ftype[MAXDATEFIELDS];
char workbuf[256];
+ DateTimeErrorExtra extra;
itm_in->tm_year = 0;
itm_in->tm_mon = 0;
{
if (dterr == DTERR_FIELD_OVERFLOW)
dterr = DTERR_INTERVAL_OVERFLOW;
- DateTimeParseError(dterr, str, "interval");
+ DateTimeParseError(dterr, &extra, str, "interval");
}
result = (Interval *) palloc(sizeof(Interval));
TimestampTz result;
char tzname[TZ_STRLEN_MAX + 1];
char *lowzone;
- int type,
+ int dterr,
+ type,
val;
pg_tz *tzp;
+ DateTimeErrorExtra extra;
/*
* timestamptz_zone() doesn't look up the zone for infinite inputs, so we
strlen(tzname),
false);
- type = DecodeTimezoneAbbrev(0, lowzone, &val, &tzp);
+ dterr = DecodeTimezoneAbbrev(0, lowzone, &type, &val, &tzp, &extra);
+ if (dterr)
+ DateTimeParseError(dterr, &extra, NULL, NULL);
if (type == TZ || type == DTZ)
{
int tz;
char tzname[TZ_STRLEN_MAX + 1];
char *lowzone;
- int type,
+ int dterr,
+ type,
val;
pg_tz *tzp;
+ DateTimeErrorExtra extra;
struct pg_tm tm;
fsec_t fsec;
strlen(tzname),
false);
- type = DecodeTimezoneAbbrev(0, lowzone, &val, &tzp);
+ dterr = DecodeTimezoneAbbrev(0, lowzone, &type, &val, &tzp, &extra);
+ if (dterr)
+ DateTimeParseError(dterr, &extra, NULL, NULL);
if (type == TZ || type == DTZ)
{
int tz;
char tzname[TZ_STRLEN_MAX + 1];
char *lowzone;
- int type,
+ int dterr,
+ type,
val;
pg_tz *tzp;
+ DateTimeErrorExtra extra;
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
strlen(tzname),
false);
- type = DecodeTimezoneAbbrev(0, lowzone, &val, &tzp);
+ dterr = DecodeTimezoneAbbrev(0, lowzone, &type, &val, &tzp, &extra);
+ if (dterr)
+ DateTimeParseError(dterr, &extra, NULL, NULL);
if (type == TZ || type == DTZ)
{
* Datetime input parsing routines (ParseDateTime, DecodeDateTime, etc)
* return zero or a positive value on success. On failure, they return
* one of these negative code values. DateTimeParseError may be used to
- * produce a correct ereport.
+ * produce a suitable error report. For some of these codes,
+ * DateTimeParseError requires additional information, which is carried
+ * in struct DateTimeErrorExtra.
*/
#define DTERR_BAD_FORMAT (-1)
#define DTERR_FIELD_OVERFLOW (-2)
#define DTERR_MD_FIELD_OVERFLOW (-3) /* triggers hint about DateStyle */
#define DTERR_INTERVAL_OVERFLOW (-4)
#define DTERR_TZDISP_OVERFLOW (-5)
+#define DTERR_BAD_TIMEZONE (-6)
+#define DTERR_BAD_ZONE_ABBREV (-7)
+
+typedef struct DateTimeErrorExtra
+{
+ /* Needed for DTERR_BAD_TIMEZONE and DTERR_BAD_ZONE_ABBREV: */
+ const char *dtee_timezone; /* incorrect time zone name */
+ /* Needed for DTERR_BAD_ZONE_ABBREV: */
+ const char *dtee_abbrev; /* relevant time zone abbreviation */
+} DateTimeErrorExtra;
extern void GetCurrentDateTime(struct pg_tm *tm);
extern int ParseDateTime(const char *timestr, char *workbuf, size_t buflen,
char **field, int *ftype,
int maxfields, int *numfields);
-extern int DecodeDateTime(char **field, int *ftype,
- int nf, int *dtype,
- struct pg_tm *tm, fsec_t *fsec, int *tzp);
+extern int DecodeDateTime(char **field, int *ftype, int nf,
+ int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
+ DateTimeErrorExtra *extra);
extern int DecodeTimezone(const char *str, int *tzp);
-extern int DecodeTimeOnly(char **field, int *ftype,
- int nf, int *dtype,
- struct pg_tm *tm, fsec_t *fsec, int *tzp);
+extern int DecodeTimeOnly(char **field, int *ftype, int nf,
+ int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
+ DateTimeErrorExtra *extra);
extern int DecodeInterval(char **field, int *ftype, int nf, int range,
int *dtype, struct pg_itm_in *itm_in);
extern int DecodeISO8601Interval(char *str,
int *dtype, struct pg_itm_in *itm_in);
-extern void DateTimeParseError(int dterr, const char *str,
+extern void DateTimeParseError(int dterr, DateTimeErrorExtra *extra,
+ const char *str,
const char *datatype) pg_attribute_noreturn();
extern int DetermineTimeZoneOffset(struct pg_tm *tm, pg_tz *tzp);
struct pg_tm *tm);
extern int DecodeTimezoneAbbrev(int field, const char *lowtoken,
- int *offset, pg_tz **tz);
+ int *ftype, int *offset, pg_tz **tz,
+ DateTimeErrorExtra *extra);
extern int DecodeSpecial(int field, const char *lowtoken, int *val);
extern int DecodeUnits(int field, const char *lowtoken, int *val);