* datetime.c
* Support functions for date/time types.
*
- * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.212 2010/05/09 02:15:59 tgl Exp $
+ * src/backend/utils/adt/datetime.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <ctype.h>
-#include <float.h>
#include <limits.h>
#include <math.h>
+#include "access/htup_details.h"
#include "access/xact.h"
#include "catalog/pg_type.h"
+#include "common/string.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
#include "utils/builtins.h"
#include "utils/date.h"
#include "utils/datetime.h"
#include "utils/tzparser.h"
-static int DecodeNumber(int flen, char *field, bool haveTextMonth,
- int fmask, int *tmask,
- struct pg_tm * tm, fsec_t *fsec, bool *is2digits);
-static int DecodeNumberField(int len, char *str,
- int fmask, int *tmask,
- struct pg_tm * tm, fsec_t *fsec, bool *is2digits);
-static int DecodeTime(char *str, int fmask, int range,
- int *tmask, struct pg_tm * tm, fsec_t *fsec);
-static int DecodeTimezone(char *str, int *tzp);
+static int DecodeNumber(int flen, char *field, bool haveTextMonth,
+ int fmask, int *tmask,
+ struct pg_tm *tm, fsec_t *fsec, bool *is2digits);
+static int DecodeNumberField(int len, char *str,
+ int fmask, int *tmask,
+ struct pg_tm *tm, fsec_t *fsec, bool *is2digits);
+static int DecodeTime(char *str, int fmask, int range,
+ int *tmask, struct pg_tm *tm, fsec_t *fsec);
static const datetkn *datebsearch(const char *key, const datetkn *base, int nel);
-static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
- struct pg_tm * tm);
-static int ValidateDate(int fmask, bool is2digits, bool bc,
- struct pg_tm * tm);
-static void TrimTrailingZeros(char *str);
-static void AppendSeconds(char *cp, int sec, fsec_t fsec,
- int precision, bool fillzeros);
-static void AdjustFractSeconds(double frac, struct pg_tm * tm, fsec_t *fsec,
- int scale);
-static void AdjustFractDays(double frac, struct pg_tm * tm, fsec_t *fsec,
- int scale);
+static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
+ struct pg_tm *tm);
+static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
+ int precision, bool fillzeros);
+static void AdjustFractSeconds(double frac, struct pg_tm *tm, fsec_t *fsec,
+ int scale);
+static void AdjustFractDays(double frac, struct pg_tm *tm, fsec_t *fsec,
+ int scale);
+static int DetermineTimeZoneOffsetInternal(struct pg_tm *tm, pg_tz *tzp,
+ pg_time_t *tp);
+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);
const int day_tab[2][13] =
{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0}
};
-char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+const char *const months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL};
-char *days[] = {"Sunday", "Monday", "Tuesday", "Wednesday",
+const char *const days[] = {"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday", NULL};
* PRIVATE ROUTINES *
*****************************************************************************/
-/*
- * Definitions for squeezing values into "value"
- * We set aside a high bit for a sign, and scale the timezone offsets
- * in minutes by a factor of 15 (so can represent quarter-hour increments).
- */
-#define ABS_SIGNBIT ((char) 0200)
-#define VALMASK ((char) 0177)
-#define POS(n) (n)
-#define NEG(n) ((n)|ABS_SIGNBIT)
-#define SIGNEDCHAR(c) ((c)&ABS_SIGNBIT? -((c)&VALMASK): (c))
-#define FROMVAL(tp) (-SIGNEDCHAR((tp)->value) * 15) /* uncompress */
-#define TOVAL(tp, v) ((tp)->value = ((v) < 0? NEG((-(v))/15): POS(v)/15))
-
/*
* datetktbl holds date/time keywords.
*
* Note that this table must be strictly alphabetically ordered to allow an
* O(ln(N)) search algorithm to be used.
*
- * The text field is NOT guaranteed to be NULL-terminated.
- *
- * To keep this table reasonably small, we divide the lexval for TZ and DTZ
- * entries by 15 (so they are on 15 minute boundaries) and truncate the text
- * field at TOKMAXLEN characters.
- * Formerly, we divided by 10 rather than 15 but there are a few time zones
- * which are 30 or 45 minutes away from an even hour, most are on an hour
- * boundary, and none on other boundaries.
+ * The token field must be NUL-terminated; we truncate entries to TOKMAXLEN
+ * characters to fit.
*
- * The static table contains no TZ or DTZ entries, rather those are loaded
- * from configuration files and stored in timezonetktbl, which has the same
- * format as the static datetktbl.
+ * The static table contains no TZ, DTZ, or DYNTZ entries; rather those
+ * are loaded from configuration files and stored in zoneabbrevtbl, whose
+ * abbrevs[] field has the same format as the static datetktbl.
*/
-static datetkn *timezonetktbl = NULL;
-
-static int sztimezonetktbl = 0;
-
static const datetkn datetktbl[] = {
-/* text, token, lexval */
+ /* token, type, value */
{EARLY, RESERV, DTK_EARLY}, /* "-infinity" reserved for "early time" */
{DA_D, ADBC, AD}, /* "ad" for years > 0 */
- {"allballs", RESERV, DTK_ZULU}, /* 00:00:00 */
+ {"allballs", RESERV, DTK_ZULU}, /* 00:00:00 */
{"am", AMPM, AM},
{"apr", MONTH, 4},
{"april", MONTH, 4},
{"aug", MONTH, 8},
{"august", MONTH, 8},
{DB_C, ADBC, BC}, /* "bc" for years <= 0 */
- {DCURRENT, RESERV, DTK_CURRENT}, /* "current" is always now */
{"d", UNITS, DTK_DAY}, /* "day of month" for ISO input */
{"dec", MONTH, 12},
{"december", MONTH, 12},
- {"dow", RESERV, DTK_DOW}, /* day of week */
- {"doy", RESERV, DTK_DOY}, /* day of year */
- {"dst", DTZMOD, 6},
+ {"dow", UNITS, DTK_DOW}, /* day of week */
+ {"doy", UNITS, DTK_DOY}, /* day of year */
+ {"dst", DTZMOD, SECS_PER_HOUR},
{EPOCH, RESERV, DTK_EPOCH}, /* "epoch" reserved for system epoch time */
{"feb", MONTH, 2},
{"february", MONTH, 2},
{"friday", DOW, 5},
{"h", UNITS, DTK_HOUR}, /* "hour" */
{LATE, RESERV, DTK_LATE}, /* "infinity" reserved for "late time" */
- {INVALID, RESERV, DTK_INVALID}, /* "invalid" reserved for bad time */
- {"isodow", RESERV, DTK_ISODOW}, /* ISO day of week, Sunday == 7 */
+ {"isodow", UNITS, DTK_ISODOW}, /* ISO day of week, Sunday == 7 */
{"isoyear", UNITS, DTK_ISOYEAR}, /* year in terms of the ISO week date */
{"j", UNITS, DTK_JULIAN},
{"jan", MONTH, 1},
{"tue", DOW, 2},
{"tues", DOW, 2},
{"tuesday", DOW, 2},
- {"undefined", RESERV, DTK_INVALID}, /* pre-v6.1 invalid time */
{"wed", DOW, 3},
{"wednesday", DOW, 3},
{"weds", DOW, 3},
{YESTERDAY, RESERV, DTK_YESTERDAY} /* yesterday midnight */
};
-static int szdatetktbl = sizeof datetktbl / sizeof datetktbl[0];
+static const int szdatetktbl = sizeof datetktbl / sizeof datetktbl[0];
-static datetkn deltatktbl[] = {
- /* text, token, lexval */
+/*
+ * deltatktbl: same format as datetktbl, but holds keywords used to represent
+ * time units (eg, for intervals, and for EXTRACT).
+ */
+static const datetkn deltatktbl[] = {
+ /* token, type, value */
{"@", IGNORE_DTF, 0}, /* postgres relative prefix */
{DAGO, AGO, 0}, /* "ago" indicates negative time offset */
{"c", UNITS, DTK_CENTURY}, /* "century" relative */
- {"cent", UNITS, DTK_CENTURY}, /* "century" relative */
+ {"cent", UNITS, DTK_CENTURY}, /* "century" relative */
{"centuries", UNITS, DTK_CENTURY}, /* "centuries" relative */
- {DCENTURY, UNITS, DTK_CENTURY}, /* "century" relative */
+ {DCENTURY, UNITS, DTK_CENTURY}, /* "century" relative */
{"d", UNITS, DTK_DAY}, /* "day" relative */
{DDAY, UNITS, DTK_DAY}, /* "day" relative */
{"days", UNITS, DTK_DAY}, /* "days" relative */
{"dec", UNITS, DTK_DECADE}, /* "decade" relative */
- {DDECADE, UNITS, DTK_DECADE}, /* "decade" relative */
- {"decades", UNITS, DTK_DECADE}, /* "decades" relative */
+ {DDECADE, UNITS, DTK_DECADE}, /* "decade" relative */
+ {"decades", UNITS, DTK_DECADE}, /* "decades" relative */
{"decs", UNITS, DTK_DECADE}, /* "decades" relative */
{"h", UNITS, DTK_HOUR}, /* "hour" relative */
{DHOUR, UNITS, DTK_HOUR}, /* "hour" relative */
{"hours", UNITS, DTK_HOUR}, /* "hours" relative */
{"hr", UNITS, DTK_HOUR}, /* "hour" relative */
{"hrs", UNITS, DTK_HOUR}, /* "hours" relative */
- {INVALID, RESERV, DTK_INVALID}, /* reserved for invalid time */
{"m", UNITS, DTK_MINUTE}, /* "minute" relative */
- {"microsecon", UNITS, DTK_MICROSEC}, /* "microsecond" relative */
- {"mil", UNITS, DTK_MILLENNIUM}, /* "millennium" relative */
- {"millennia", UNITS, DTK_MILLENNIUM}, /* "millennia" relative */
- {DMILLENNIUM, UNITS, DTK_MILLENNIUM}, /* "millennium" relative */
- {"millisecon", UNITS, DTK_MILLISEC}, /* relative */
+ {"microsecon", UNITS, DTK_MICROSEC}, /* "microsecond" relative */
+ {"mil", UNITS, DTK_MILLENNIUM}, /* "millennium" relative */
+ {"millennia", UNITS, DTK_MILLENNIUM}, /* "millennia" relative */
+ {DMILLENNIUM, UNITS, DTK_MILLENNIUM}, /* "millennium" relative */
+ {"millisecon", UNITS, DTK_MILLISEC}, /* relative */
{"mils", UNITS, DTK_MILLENNIUM}, /* "millennia" relative */
{"min", UNITS, DTK_MINUTE}, /* "minute" relative */
{"mins", UNITS, DTK_MINUTE}, /* "minutes" relative */
- {DMINUTE, UNITS, DTK_MINUTE}, /* "minute" relative */
- {"minutes", UNITS, DTK_MINUTE}, /* "minutes" relative */
+ {DMINUTE, UNITS, DTK_MINUTE}, /* "minute" relative */
+ {"minutes", UNITS, DTK_MINUTE}, /* "minutes" relative */
{"mon", UNITS, DTK_MONTH}, /* "months" relative */
{"mons", UNITS, DTK_MONTH}, /* "months" relative */
{DMONTH, UNITS, DTK_MONTH}, /* "month" relative */
{"mseconds", UNITS, DTK_MILLISEC},
{"msecs", UNITS, DTK_MILLISEC},
{"qtr", UNITS, DTK_QUARTER}, /* "quarter" relative */
- {DQUARTER, UNITS, DTK_QUARTER}, /* "quarter" relative */
+ {DQUARTER, UNITS, DTK_QUARTER}, /* "quarter" relative */
{"s", UNITS, DTK_SECOND},
{"sec", UNITS, DTK_SECOND},
{DSECOND, UNITS, DTK_SECOND},
{"secs", UNITS, DTK_SECOND},
{DTIMEZONE, UNITS, DTK_TZ}, /* "timezone" time offset */
{"timezone_h", UNITS, DTK_TZ_HOUR}, /* timezone hour units */
- {"timezone_m", UNITS, DTK_TZ_MINUTE}, /* timezone minutes units */
- {"undefined", RESERV, DTK_INVALID}, /* pre-v6.1 invalid time */
+ {"timezone_m", UNITS, DTK_TZ_MINUTE}, /* timezone minutes units */
{"us", UNITS, DTK_MICROSEC}, /* "microsecond" relative */
- {"usec", UNITS, DTK_MICROSEC}, /* "microsecond" relative */
+ {"usec", UNITS, DTK_MICROSEC}, /* "microsecond" relative */
{DMICROSEC, UNITS, DTK_MICROSEC}, /* "microsecond" relative */
{"useconds", UNITS, DTK_MICROSEC}, /* "microseconds" relative */
- {"usecs", UNITS, DTK_MICROSEC}, /* "microseconds" relative */
+ {"usecs", UNITS, DTK_MICROSEC}, /* "microseconds" relative */
{"w", UNITS, DTK_WEEK}, /* "week" relative */
{DWEEK, UNITS, DTK_WEEK}, /* "week" relative */
{"weeks", UNITS, DTK_WEEK}, /* "weeks" relative */
{"yrs", UNITS, DTK_YEAR} /* "years" relative */
};
-static int szdeltatktbl = sizeof deltatktbl / sizeof deltatktbl[0];
+static const int szdeltatktbl = sizeof deltatktbl / sizeof deltatktbl[0];
+
+static TimeZoneAbbrevTable *zoneabbrevtbl = NULL;
+
+/* Caches of recent lookup results in the above tables */
static const datetkn *datecache[MAXDATEFIELDS] = {NULL};
static const datetkn *deltacache[MAXDATEFIELDS] = {NULL};
-
-/*
- * strtoi --- just like strtol, but returns int not long
- */
-static int
-strtoi(const char *nptr, char **endptr, int base)
-{
- long val;
-
- val = strtol(nptr, endptr, base);
-#ifdef HAVE_LONG_INT_64
- if (val != (long) ((int32) val))
- errno = ERANGE;
-#endif
- return (int) val;
-}
+static const datetkn *abbrevcache[MAXDATEFIELDS] = {NULL};
/*
* and calendar date for all non-negative Julian days
* (i.e. from Nov 24, -4713 on).
*
- * These routines will be used by other date/time packages
- * - thomas 97/02/25
- *
* Rewritten to eliminate overflow problems. This now allows the
* routines to work correctly for all Julian day counts from
* 0 to 2147483647 (Nov 24, -4713 to Jun 3, 5874898) assuming
* a 32-bit integer. Longer types should also work to the limits
* of their precision.
+ *
+ * Actually, date2j() will work sanely, in the sense of producing
+ * valid negative Julian dates, significantly before Nov 24, -4713.
+ * We rely on it to do so back to Nov 1, -4713; see IS_VALID_JULIAN()
+ * and associated commentary in timestamp.h.
*/
int
julian += 7834 * m / 256 + d;
return julian;
-} /* date2j() */
+} /* date2j() */
void
j2date(int jd, int *year, int *month, int *day)
*year = y - 4800;
quad = julian * 2141 / 65536;
*day = julian - 7834 * quad / 256;
- *month = (quad + 10) % 12 + 1;
+ *month = (quad + 10) % MONTHS_PER_YEAR + 1;
return;
-} /* j2date() */
+} /* j2date() */
/*
* j2day - convert Julian date to day-of-week (0..6 == Sun..Sat)
*
* Note: various places use the locution j2day(date - 1) to produce a
- * result according to the convention 0..6 = Mon..Sun. This is a bit of
+ * result according to the convention 0..6 = Mon..Sun. This is a bit of
* a crock, but will work as long as the computation here is just a modulo.
*/
int
j2day(int date)
{
- unsigned int day;
-
- day = date;
+ date += 1;
+ date %= 7;
+ /* Cope if division truncates towards zero, as it probably does */
+ if (date < 0)
+ date += 7;
- day += 1;
- day %= 7;
-
- return (int) day;
-} /* j2day() */
+ return date;
+} /* j2day() */
/*
* Get the transaction start time ("now()") broken down as a struct pg_tm.
*/
void
-GetCurrentDateTime(struct pg_tm * tm)
+GetCurrentDateTime(struct pg_tm *tm)
{
int tz;
fsec_t fsec;
* including fractional seconds and timezone offset.
*/
void
-GetCurrentTimeUsec(struct pg_tm * tm, fsec_t *fsec, int *tzp)
+GetCurrentTimeUsec(struct pg_tm *tm, fsec_t *fsec, int *tzp)
{
int tz;
}
-/* TrimTrailingZeros()
- * ... resulting from printing numbers with full precision.
- *
- * Before Postgres 8.4, this always left at least 2 fractional digits,
- * but conversations on the lists suggest this isn't desired
- * since showing '0.10' is misleading with values of precision(1).
- */
-static void
-TrimTrailingZeros(char *str)
-{
- int len = strlen(str);
-
- while (len > 1 && *(str + len - 1) == '0' && *(str + len - 2) != '.')
- {
- len--;
- *(str + len) = '\0';
- }
-}
-
/*
- * Append sections and fractional seconds (if any) at *cp.
+ * Append seconds and fractional seconds (if any) at *cp.
+ *
* precision is the max number of fraction digits, fillzeros says to
* pad to two integral-seconds digits.
+ *
+ * Returns a pointer to the new end of string. No NUL terminator is put
+ * there; callers are responsible for NUL terminating str themselves.
+ *
* Note that any sign is stripped from the input seconds values.
*/
-static void
+static char *
AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros)
{
- if (fsec == 0)
- {
- if (fillzeros)
- sprintf(cp, "%02d", abs(sec));
- else
- sprintf(cp, "%d", abs(sec));
- }
+ Assert(precision >= 0);
+
+ if (fillzeros)
+ cp = pg_ltostr_zeropad(cp, Abs(sec), 2);
else
+ cp = pg_ltostr(cp, Abs(sec));
+
+ /* fsec_t is just an int32 */
+ if (fsec != 0)
{
-#ifdef HAVE_INT64_TIMESTAMP
- if (fillzeros)
- sprintf(cp, "%02d.%0*d", abs(sec), precision, (int) Abs(fsec));
- else
- sprintf(cp, "%d.%0*d", abs(sec), precision, (int) Abs(fsec));
-#else
- if (fillzeros)
- sprintf(cp, "%0*.*f", precision + 3, precision, fabs(sec + fsec));
- else
- sprintf(cp, "%.*f", precision, fabs(sec + fsec));
-#endif
- TrimTrailingZeros(cp);
+ int32 value = Abs(fsec);
+ char *end = &cp[precision + 1];
+ bool gotnonzero = false;
+
+ *cp++ = '.';
+
+ /*
+ * Append the fractional seconds part. Note that we don't want any
+ * trailing zeros here, so since we're building the number in reverse
+ * we'll skip appending zeros until we've output a non-zero digit.
+ */
+ while (precision--)
+ {
+ int32 oldval = value;
+ int32 remainder;
+
+ value /= 10;
+ remainder = oldval - value * 10;
+
+ /* check if we got a non-zero */
+ if (remainder)
+ gotnonzero = true;
+
+ if (gotnonzero)
+ cp[precision] = '0' + remainder;
+ else
+ end = &cp[precision];
+ }
+
+ /*
+ * If we still have a non-zero value then precision must have not been
+ * enough to print the number. We punt the problem to pg_ltostr(),
+ * which will generate a correct answer in the minimum valid width.
+ */
+ if (value)
+ return pg_ltostr(cp, Abs(fsec));
+
+ return end;
}
+ else
+ return cp;
}
-/* Variant of above that's specialized to timestamp case */
-static void
-AppendTimestampSeconds(char *cp, struct pg_tm * tm, fsec_t fsec)
+
+/*
+ * Variant of above that's specialized to timestamp case.
+ *
+ * Returns a pointer to the new end of string. No NUL terminator is put
+ * there; callers are responsible for NUL terminating str themselves.
+ */
+static char *
+AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
{
- /*
- * In float mode, don't print fractional seconds before 1 AD, since it's
- * unlikely there's any precision left ...
- */
-#ifndef HAVE_INT64_TIMESTAMP
- if (tm->tm_year <= 0)
- fsec = 0;
-#endif
- AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
+ return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
}
/*
* We assume the input frac is less than 1 so overflow is not an issue.
*/
static void
-AdjustFractSeconds(double frac, struct pg_tm * tm, fsec_t *fsec, int scale)
+AdjustFractSeconds(double frac, struct pg_tm *tm, fsec_t *fsec, int scale)
{
int sec;
sec = (int) frac;
tm->tm_sec += sec;
frac -= sec;
-#ifdef HAVE_INT64_TIMESTAMP
*fsec += rint(frac * 1000000);
-#else
- *fsec += frac;
-#endif
}
/* As above, but initial scale produces days */
static void
-AdjustFractDays(double frac, struct pg_tm * tm, fsec_t *fsec, int scale)
+AdjustFractDays(double frac, struct pg_tm *tm, fsec_t *fsec, int scale)
{
int extra_days;
/* check for parse failure */
if (*cp != '\0' || errno != 0)
return DTERR_BAD_FORMAT;
-#ifdef HAVE_INT64_TIMESTAMP
*fsec = rint(frac * 1000000);
-#else
- *fsec = frac;
-#endif
return 0;
}
*/
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)
{
int fmask = 0,
tmask,
int val;
int dterr;
int mer = HR24;
- bool haveTextMonth = FALSE;
- bool is2digits = FALSE;
- bool bc = FALSE;
+ bool haveTextMonth = false;
+ bool isjulian = false;
+ bool is2digits = false;
+ bool bc = false;
pg_tz *namedTz = NULL;
+ pg_tz *abbrevTz = NULL;
+ pg_tz *valtz;
+ char *abbrev = NULL;
+ struct pg_tm cur_tm;
/*
* We'll insist on at least all of the date fields, but initialize the
switch (ftype[i])
{
case DTK_DATE:
- /***
- * Integral julian day with attached time zone?
- * All other forms with JD will be separated into
- * distinct fields, so we handle just this case here.
- ***/
+
+ /*
+ * Integral julian day with attached time zone? All other
+ * forms with JD will be separated into distinct fields, so we
+ * handle just this case here.
+ */
if (ptype == DTK_JULIAN)
{
char *cp;
return DTERR_BAD_FORMAT;
errno = 0;
- val = strtoi(field[i], &cp, 10);
- if (errno == ERANGE)
+ val = strtoint(field[i], &cp, 10);
+ if (errno == ERANGE || val < 0)
return DTERR_FIELD_OVERFLOW;
j2date(val, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
+ isjulian = true;
+
/* Get the time zone from the end of the string */
dterr = DecodeTimezone(cp, tzp);
if (dterr)
ptype = 0;
break;
}
- /***
+
+ /*
* Already have a date? Then this might be a time zone name
* with embedded punctuation (e.g. "America/New_York") or a
* run-together time with trailing time zone (e.g. hhmmss-zz).
* We consider it a time zone if we already have month & day.
* This is to allow the form "mmm dd hhmmss tz year", which
* we've historically accepted.
- ***/
+ */
else if (ptype != 0 ||
((fmask & (DTK_M(MONTH) | DTK_M(DAY))) ==
(DTK_M(MONTH) | DTK_M(DAY))))
break;
case DTK_TIME:
+
+ /*
+ * This might be an ISO time following a "t" field.
+ */
+ if (ptype != 0)
+ {
+ /* Sanity check; should not fail this test */
+ if (ptype != DTK_TIME)
+ return DTERR_BAD_FORMAT;
+ ptype = 0;
+ }
dterr = DecodeTime(field[i], fmask, INTERVAL_FULL_RANGE,
&tmask, tm, fsec);
if (dterr)
* DecodeTime()
*/
/* test for > 24:00:00 */
- if (tm->tm_hour > 24 ||
- (tm->tm_hour == 24 &&
+ if (tm->tm_hour > HOURS_PER_DAY ||
+ (tm->tm_hour == HOURS_PER_DAY &&
(tm->tm_min > 0 || tm->tm_sec > 0 || *fsec > 0)))
return DTERR_FIELD_OVERFLOW;
break;
int val;
errno = 0;
- val = strtoi(field[i], &cp, 10);
+ val = strtoint(field[i], &cp, 10);
if (errno == ERANGE)
return DTERR_FIELD_OVERFLOW;
break;
case DTK_JULIAN:
- /***
- * previous field was a label for "julian date"?
- ***/
+ /* previous field was a label for "julian date" */
+ if (val < 0)
+ return DTERR_FIELD_OVERFLOW;
tmask = DTK_DATE_M;
j2date(val, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
+ isjulian = true;
+
/* fractional Julian Day? */
if (*cp == '.')
{
time = strtod(cp, &cp);
if (*cp != '\0' || errno != 0)
return DTERR_BAD_FORMAT;
-
-#ifdef HAVE_INT64_TIMESTAMP
time *= USECS_PER_DAY;
-#else
- time *= SECS_PER_DAY;
-#endif
dt2time(time,
&tm->tm_hour, &tm->tm_min,
&tm->tm_sec, fsec);
if (dterr < 0)
return dterr;
}
- else if (flen > 4)
+
+ /*
+ * Is this a YMD or HMS specification, or a year number?
+ * YMD and HMS are required to be six digits or more, so
+ * if it is 5 digits, it is a year. If it is six or more
+ * digits, we assume it is YMD or HMS unless no date and
+ * no time values have been specified. This forces 6+
+ * digit years to be at the end of the string, or to use
+ * the ISO date specification.
+ */
+ else if (flen >= 6 && (!(fmask & DTK_DATE_M) ||
+ !(fmask & DTK_TIME_M)))
{
dterr = DecodeNumberField(flen, field[i], fmask,
&tmask, tm,
case DTK_STRING:
case DTK_SPECIAL:
- type = DecodeSpecial(i, field[i], &val);
+ /* timezone abbrevs take precedence over built-in tokens */
+ type = DecodeTimezoneAbbrev(i, field[i], &val, &valtz);
+ if (type == UNKNOWN_FIELD)
+ type = DecodeSpecial(i, field[i], &val);
if (type == IGNORE_DTF)
continue;
case RESERV:
switch (val)
{
- case DTK_CURRENT:
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("date/time value \"current\" is no longer supported")));
-
- return DTERR_BAD_FORMAT;
- break;
-
case DTK_NOW:
tmask = (DTK_DATE_M | DTK_TIME_M | DTK_M(TZ));
*dtype = DTK_DATE;
case DTK_YESTERDAY:
tmask = DTK_DATE_M;
*dtype = DTK_DATE;
- GetCurrentDateTime(tm);
- j2date(date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - 1,
- &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
- tm->tm_hour = 0;
- tm->tm_min = 0;
- tm->tm_sec = 0;
+ GetCurrentDateTime(&cur_tm);
+ j2date(date2j(cur_tm.tm_year, cur_tm.tm_mon, cur_tm.tm_mday) - 1,
+ &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
break;
case DTK_TODAY:
tmask = DTK_DATE_M;
*dtype = DTK_DATE;
- GetCurrentDateTime(tm);
- tm->tm_hour = 0;
- tm->tm_min = 0;
- tm->tm_sec = 0;
+ GetCurrentDateTime(&cur_tm);
+ tm->tm_year = cur_tm.tm_year;
+ tm->tm_mon = cur_tm.tm_mon;
+ tm->tm_mday = cur_tm.tm_mday;
break;
case DTK_TOMORROW:
tmask = DTK_DATE_M;
*dtype = DTK_DATE;
- GetCurrentDateTime(tm);
- j2date(date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) + 1,
- &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
- tm->tm_hour = 0;
- tm->tm_min = 0;
- tm->tm_sec = 0;
+ GetCurrentDateTime(&cur_tm);
+ j2date(date2j(cur_tm.tm_year, cur_tm.tm_mon, cur_tm.tm_mday) + 1,
+ &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
break;
case DTK_ZULU:
tm->tm_mday = tm->tm_mon;
tmask = DTK_M(DAY);
}
- haveTextMonth = TRUE;
+ haveTextMonth = true;
tm->tm_mon = val;
break;
tm->tm_isdst = 1;
if (tzp == NULL)
return DTERR_BAD_FORMAT;
- *tzp += val * MINS_PER_HOUR;
+ *tzp -= val;
break;
case DTZ:
tm->tm_isdst = 1;
if (tzp == NULL)
return DTERR_BAD_FORMAT;
- *tzp = val * MINS_PER_HOUR;
+ *tzp = -val;
break;
case TZ:
tm->tm_isdst = 0;
if (tzp == NULL)
return DTERR_BAD_FORMAT;
- *tzp = val * MINS_PER_HOUR;
+ *tzp = -val;
break;
- case IGNORE_DTF:
+ case DYNTZ:
+ tmask |= DTK_M(TZ);
+ if (tzp == NULL)
+ return DTERR_BAD_FORMAT;
+ /* we'll determine the actual offset later */
+ abbrevTz = valtz;
+ abbrev = field[i];
break;
case AMPM:
} /* end loop over fields */
/* do final checking/adjustment of Y/M/D fields */
- dterr = ValidateDate(fmask, is2digits, bc, tm);
+ dterr = ValidateDate(fmask, isjulian, is2digits, bc, tm);
if (dterr)
return dterr;
/* handle AM/PM */
- if (mer != HR24 && tm->tm_hour > 12)
+ if (mer != HR24 && tm->tm_hour > HOURS_PER_DAY / 2)
return DTERR_FIELD_OVERFLOW;
- if (mer == AM && tm->tm_hour == 12)
+ if (mer == AM && tm->tm_hour == HOURS_PER_DAY / 2)
tm->tm_hour = 0;
- else if (mer == PM && tm->tm_hour != 12)
- tm->tm_hour += 12;
+ else if (mer == PM && tm->tm_hour != HOURS_PER_DAY / 2)
+ tm->tm_hour += HOURS_PER_DAY / 2;
/* do additional checking for full date specs... */
if (*dtype == DTK_DATE)
*tzp = DetermineTimeZoneOffset(tm, namedTz);
}
- /* timezone not specified? then find local timezone if possible */
+ /*
+ * Likewise, if we had a dynamic timezone abbreviation, resolve it
+ * now.
+ */
+ if (abbrevTz != NULL)
+ {
+ /* daylight savings time modifier disallowed with dynamic TZ */
+ if (fmask & DTK_M(DTZMOD))
+ return DTERR_BAD_FORMAT;
+
+ *tzp = DetermineTimeZoneAbbrevOffset(tm, abbrev, abbrevTz);
+ }
+
+ /* timezone not specified? then use session timezone */
if (tzp != NULL && !(fmask & DTK_M(TZ)))
{
/*
/* DetermineTimeZoneOffset()
*
- * Given a struct pg_tm in which tm_year, tm_mon, tm_mday, tm_hour, tm_min, and
- * tm_sec fields are set, attempt to determine the applicable time zone
- * (ie, regular or daylight-savings time) at that time. Set the struct pg_tm's
- * tm_isdst field accordingly, and return the actual timezone offset.
+ * Given a struct pg_tm in which tm_year, tm_mon, tm_mday, tm_hour, tm_min,
+ * and tm_sec fields are set, and a zic-style time zone definition, determine
+ * the applicable GMT offset and daylight-savings status at that time.
+ * Set the struct pg_tm's tm_isdst field accordingly, and return the GMT
+ * offset as the function result.
+ *
+ * Note: if the date is out of the range we can deal with, we return zero
+ * as the GMT offset and set tm_isdst = 0. We don't throw an error here,
+ * though probably some higher-level code will.
+ */
+int
+DetermineTimeZoneOffset(struct pg_tm *tm, pg_tz *tzp)
+{
+ pg_time_t t;
+
+ return DetermineTimeZoneOffsetInternal(tm, tzp, &t);
+}
+
+
+/* DetermineTimeZoneOffsetInternal()
+ *
+ * As above, but also return the actual UTC time imputed to the date/time
+ * into *tp.
+ *
+ * In event of an out-of-range date, we punt by returning zero into *tp.
+ * This is okay for the immediate callers but is a good reason for not
+ * exposing this worker function globally.
*
* Note: it might seem that we should use mktime() for this, but bitter
* experience teaches otherwise. This code is much faster than most versions
* of mktime(), anyway.
*/
-int
-DetermineTimeZoneOffset(struct pg_tm * tm, pg_tz *tzp)
+static int
+DetermineTimeZoneOffsetInternal(struct pg_tm *tm, pg_tz *tzp, pg_time_t *tp)
{
int date,
sec;
after_isdst;
int res;
- if (tzp == session_timezone && HasCTZSet)
- {
- tm->tm_isdst = 0; /* for lack of a better idea */
- return CTimeZone;
- }
-
/*
* First, generate the pg_time_t value corresponding to the given
* y/m/d/h/m/s taken as GMT time. If this overflows, punt and decide the
- * timezone is GMT. (We only need to worry about overflow on machines
- * where pg_time_t is 32 bits.)
+ * timezone is GMT. (For a valid Julian date, integer overflow should be
+ * impossible with 64-bit pg_time_t, but let's check for safety.)
*/
if (!IS_VALID_JULIAN(tm->tm_year, tm->tm_mon, tm->tm_mday))
goto overflow;
{
/* Non-DST zone, life is simple */
tm->tm_isdst = before_isdst;
+ *tp = mytime - before_gmtoff;
return -(int) before_gmtoff;
}
goto overflow;
/*
- * If both before or both after the boundary time, we know what to do
+ * If both before or both after the boundary time, we know what to do. The
+ * boundary time itself is considered to be after the transition, which
+ * means we can accept aftertime == boundary in the second case.
*/
- if (beforetime <= boundary && aftertime < boundary)
+ if (beforetime < boundary && aftertime < boundary)
{
tm->tm_isdst = before_isdst;
+ *tp = beforetime;
return -(int) before_gmtoff;
}
if (beforetime > boundary && aftertime >= boundary)
{
tm->tm_isdst = after_isdst;
+ *tp = aftertime;
return -(int) after_gmtoff;
}
/*
- * It's an invalid or ambiguous time due to timezone transition. Prefer
- * the standard-time interpretation.
+ * It's an invalid or ambiguous time due to timezone transition. In a
+ * spring-forward transition, prefer the "before" interpretation; in a
+ * fall-back transition, prefer "after". (We used to define and implement
+ * this test as "prefer the standard-time interpretation", but that rule
+ * does not help to resolve the behavior when both times are reported as
+ * standard time; which does happen, eg Europe/Moscow in Oct 2014. Also,
+ * in some zones such as Europe/Dublin, there is widespread confusion
+ * about which time offset is "standard" time, so it's fortunate that our
+ * behavior doesn't depend on that.)
*/
- if (after_isdst == 0)
+ if (beforetime > aftertime)
{
- tm->tm_isdst = after_isdst;
- return -(int) after_gmtoff;
+ tm->tm_isdst = before_isdst;
+ *tp = beforetime;
+ return -(int) before_gmtoff;
}
- tm->tm_isdst = before_isdst;
- return -(int) before_gmtoff;
+ tm->tm_isdst = after_isdst;
+ *tp = aftertime;
+ return -(int) after_gmtoff;
overflow:
/* Given date is out of range, so assume UTC */
tm->tm_isdst = 0;
+ *tp = 0;
return 0;
}
+/* DetermineTimeZoneAbbrevOffset()
+ *
+ * Determine the GMT offset and DST flag to be attributed to a dynamic
+ * time zone abbreviation, that is one whose meaning has changed over time.
+ * *tm contains the local time at which the meaning should be determined,
+ * and tm->tm_isdst receives the DST flag.
+ *
+ * This differs from the behavior of DetermineTimeZoneOffset() in that a
+ * standard-time or daylight-time abbreviation forces use of the corresponding
+ * GMT offset even when the zone was then in DS or standard time respectively.
+ * (However, that happens only if we can match the given abbreviation to some
+ * abbreviation that appears in the IANA timezone data. Otherwise, we fall
+ * back to doing DetermineTimeZoneOffset().)
+ */
+int
+DetermineTimeZoneAbbrevOffset(struct pg_tm *tm, const char *abbr, pg_tz *tzp)
+{
+ pg_time_t t;
+ int zone_offset;
+ int abbr_offset;
+ int abbr_isdst;
+
+ /*
+ * Compute the UTC time we want to probe at. (In event of overflow, we'll
+ * probe at the epoch, which is a bit random but probably doesn't matter.)
+ */
+ zone_offset = DetermineTimeZoneOffsetInternal(tm, tzp, &t);
+
+ /*
+ * Try to match the abbreviation to something in the zone definition.
+ */
+ if (DetermineTimeZoneAbbrevOffsetInternal(t, abbr, tzp,
+ &abbr_offset, &abbr_isdst))
+ {
+ /* Success, so use the abbrev-specific answers. */
+ tm->tm_isdst = abbr_isdst;
+ return abbr_offset;
+ }
+
+ /*
+ * No match, so use the answers we already got from
+ * DetermineTimeZoneOffsetInternal.
+ */
+ return zone_offset;
+}
+
+
+/* DetermineTimeZoneAbbrevOffsetTS()
+ *
+ * As above but the probe time is specified as a TimestampTz (hence, UTC time),
+ * and DST status is returned into *isdst rather than into tm->tm_isdst.
+ */
+int
+DetermineTimeZoneAbbrevOffsetTS(TimestampTz ts, const char *abbr,
+ pg_tz *tzp, int *isdst)
+{
+ pg_time_t t = timestamptz_to_time_t(ts);
+ int zone_offset;
+ int abbr_offset;
+ int tz;
+ struct pg_tm tm;
+ fsec_t fsec;
+
+ /*
+ * If the abbrev matches anything in the zone data, this is pretty easy.
+ */
+ if (DetermineTimeZoneAbbrevOffsetInternal(t, abbr, tzp,
+ &abbr_offset, isdst))
+ return abbr_offset;
+
+ /*
+ * Else, break down the timestamp so we can use DetermineTimeZoneOffset.
+ */
+ if (timestamp2tm(ts, &tz, &tm, &fsec, NULL, tzp) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+
+ zone_offset = DetermineTimeZoneOffset(&tm, tzp);
+ *isdst = tm.tm_isdst;
+ return zone_offset;
+}
+
+
+/* DetermineTimeZoneAbbrevOffsetInternal()
+ *
+ * Workhorse for above two functions: work from a pg_time_t probe instant.
+ * On success, return GMT offset and DST status into *offset and *isdst.
+ */
+static bool
+DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, const char *abbr, pg_tz *tzp,
+ int *offset, int *isdst)
+{
+ char upabbr[TZ_STRLEN_MAX + 1];
+ unsigned char *p;
+ long int gmtoff;
+
+ /* We need to force the abbrev to upper case */
+ strlcpy(upabbr, abbr, sizeof(upabbr));
+ for (p = (unsigned char *) upabbr; *p; p++)
+ *p = pg_toupper(*p);
+
+ /* Look up the abbrev's meaning at this time in this zone */
+ if (pg_interpret_timezone_abbrev(upabbr,
+ &t,
+ &gmtoff,
+ isdst,
+ tzp))
+ {
+ /* Change sign to agree with DetermineTimeZoneOffset() */
+ *offset = (int) -gmtoff;
+ return true;
+ }
+ return false;
+}
+
+
/* DecodeTimeOnly()
* Interpret parsed string as time fields only.
* Returns 0 if successful, DTERR code if bogus input detected.
*
* Note that support for time zone is here for
- * SQL92 TIME WITH TIME ZONE, but it reveals
- * bogosity with SQL92 date/time standards, since
+ * SQL TIME WITH TIME ZONE, but it reveals
+ * bogosity with SQL date/time standards, since
* we must infer a time zone from current time.
* - thomas 2000-03-10
* Allow specifying date to get a better time zone,
*/
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)
{
int fmask = 0,
tmask,
int i;
int val;
int dterr;
- bool is2digits = FALSE;
- bool bc = FALSE;
+ bool isjulian = false;
+ bool is2digits = false;
+ bool bc = false;
int mer = HR24;
pg_tz *namedTz = NULL;
+ pg_tz *abbrevTz = NULL;
+ char *abbrev = NULL;
+ pg_tz *valtz;
*dtype = DTK_TIME;
tm->tm_hour = 0;
/*
* Was this an "ISO time" with embedded field labels? An
- * example is "h04m05s06" - thomas 2001-02-04
+ * example is "h04mm05s06" - thomas 2001-02-04
*/
if (ptype != 0)
{
}
errno = 0;
- val = strtoi(field[i], &cp, 10);
+ val = strtoint(field[i], &cp, 10);
if (errno == ERANGE)
return DTERR_FIELD_OVERFLOW;
break;
case DTK_JULIAN:
- /***
- * previous field was a label for "julian date"?
- ***/
+ /* previous field was a label for "julian date" */
+ if (val < 0)
+ return DTERR_FIELD_OVERFLOW;
tmask = DTK_DATE_M;
j2date(val, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
+ isjulian = true;
+
if (*cp == '.')
{
double time;
time = strtod(cp, &cp);
if (*cp != '\0' || errno != 0)
return DTERR_BAD_FORMAT;
-
-#ifdef HAVE_INT64_TIMESTAMP
time *= USECS_PER_DAY;
-#else
- time *= SECS_PER_DAY;
-#endif
dt2time(time,
&tm->tm_hour, &tm->tm_min,
&tm->tm_sec, fsec);
else
{
dterr = DecodeNumber(flen, field[i],
- FALSE,
+ false,
(fmask | DTK_DATE_M),
&tmask, tm,
fsec, &is2digits);
case DTK_STRING:
case DTK_SPECIAL:
- type = DecodeSpecial(i, field[i], &val);
+ /* timezone abbrevs take precedence over built-in tokens */
+ type = DecodeTimezoneAbbrev(i, field[i], &val, &valtz);
+ if (type == UNKNOWN_FIELD)
+ type = DecodeSpecial(i, field[i], &val);
if (type == IGNORE_DTF)
continue;
case RESERV:
switch (val)
{
- case DTK_CURRENT:
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("date/time value \"current\" is no longer supported")));
- return DTERR_BAD_FORMAT;
- break;
-
case DTK_NOW:
tmask = DTK_TIME_M;
*dtype = DTK_TIME;
tm->tm_isdst = 1;
if (tzp == NULL)
return DTERR_BAD_FORMAT;
- *tzp += val * MINS_PER_HOUR;
+ *tzp -= val;
break;
case DTZ:
tm->tm_isdst = 1;
if (tzp == NULL)
return DTERR_BAD_FORMAT;
- *tzp = val * MINS_PER_HOUR;
+ *tzp = -val;
ftype[i] = DTK_TZ;
break;
tm->tm_isdst = 0;
if (tzp == NULL)
return DTERR_BAD_FORMAT;
- *tzp = val * MINS_PER_HOUR;
+ *tzp = -val;
ftype[i] = DTK_TZ;
break;
- case IGNORE_DTF:
+ case DYNTZ:
+ tmask |= DTK_M(TZ);
+ if (tzp == NULL)
+ return DTERR_BAD_FORMAT;
+ /* we'll determine the actual offset later */
+ abbrevTz = valtz;
+ abbrev = field[i];
+ ftype[i] = DTK_TZ;
break;
case AMPM:
} /* end loop over fields */
/* do final checking/adjustment of Y/M/D fields */
- dterr = ValidateDate(fmask, is2digits, bc, tm);
+ dterr = ValidateDate(fmask, isjulian, is2digits, bc, tm);
if (dterr)
return dterr;
/* handle AM/PM */
- if (mer != HR24 && tm->tm_hour > 12)
+ if (mer != HR24 && tm->tm_hour > HOURS_PER_DAY / 2)
return DTERR_FIELD_OVERFLOW;
- if (mer == AM && tm->tm_hour == 12)
+ if (mer == AM && tm->tm_hour == HOURS_PER_DAY / 2)
tm->tm_hour = 0;
- else if (mer == PM && tm->tm_hour != 12)
- tm->tm_hour += 12;
+ else if (mer == PM && tm->tm_hour != HOURS_PER_DAY / 2)
+ tm->tm_hour += HOURS_PER_DAY / 2;
- if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_min > 59 ||
- tm->tm_sec < 0 || tm->tm_sec > 60 || tm->tm_hour > 24 ||
+ /*
+ * This should match the checks in make_timestamp_internal
+ */
+ if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_min > MINS_PER_HOUR - 1 ||
+ tm->tm_sec < 0 || tm->tm_sec > SECS_PER_MINUTE ||
+ tm->tm_hour > HOURS_PER_DAY ||
/* test for > 24:00:00 */
- (tm->tm_hour == 24 &&
+ (tm->tm_hour == HOURS_PER_DAY &&
(tm->tm_min > 0 || tm->tm_sec > 0 || *fsec > 0)) ||
-#ifdef HAVE_INT64_TIMESTAMP
- *fsec < INT64CONST(0) || *fsec > USECS_PER_SEC
-#else
- *fsec < 0 || *fsec > 1
-#endif
- )
+ *fsec < INT64CONST(0) || *fsec > USECS_PER_SEC)
return DTERR_FIELD_OVERFLOW;
if ((fmask & DTK_TIME_M) != DTK_TIME_M)
}
}
- /* timezone not specified? then find local timezone if possible */
+ /*
+ * Likewise, if we had a dynamic timezone abbreviation, resolve it now.
+ */
+ if (abbrevTz != NULL)
+ {
+ struct pg_tm tt,
+ *tmp = &tt;
+
+ /*
+ * daylight savings time modifier but no standard timezone? then error
+ */
+ if (fmask & DTK_M(DTZMOD))
+ return DTERR_BAD_FORMAT;
+
+ if ((fmask & DTK_DATE_M) == 0)
+ GetCurrentDateTime(tmp);
+ else
+ {
+ /* a date has to be specified */
+ if ((fmask & DTK_DATE_M) != DTK_DATE_M)
+ return DTERR_BAD_FORMAT;
+ tmp->tm_year = tm->tm_year;
+ tmp->tm_mon = tm->tm_mon;
+ tmp->tm_mday = tm->tm_mday;
+ }
+ tmp->tm_hour = tm->tm_hour;
+ tmp->tm_min = tm->tm_min;
+ tmp->tm_sec = tm->tm_sec;
+ *tzp = DetermineTimeZoneAbbrevOffset(tmp, abbrev, abbrevTz);
+ tm->tm_isdst = tmp->tm_isdst;
+ }
+
+ /* timezone not specified? then use session timezone */
if (tzp != NULL && !(fmask & DTK_M(TZ)))
{
struct pg_tm tt,
GetCurrentDateTime(tmp);
else
{
+ /* a date has to be specified */
+ if ((fmask & DTK_DATE_M) != DTK_DATE_M)
+ return DTERR_BAD_FORMAT;
tmp->tm_year = tm->tm_year;
tmp->tm_mon = tm->tm_mon;
tmp->tm_mday = tm->tm_mday;
* str: field to be parsed
* fmask: bitmask for field types already seen
* *tmask: receives bitmask for fields found here
- * *is2digits: set to TRUE if we find 2-digit year
+ * *is2digits: set to true if we find 2-digit year
* *tm: field values are stored into appropriate members of this struct
*/
static int
DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
- struct pg_tm * tm)
+ struct pg_tm *tm)
{
fsec_t fsec;
int nf = 0;
int i,
len;
int dterr;
- bool haveTextMonth = FALSE;
+ bool haveTextMonth = false;
int type,
val,
dmask = 0;
while (*str != '\0' && nf < MAXDATEFIELDS)
{
/* skip field separators */
- while (!isalnum((unsigned char) *str))
+ while (*str != '\0' && !isalnum((unsigned char) *str))
str++;
+ if (*str == '\0')
+ return DTERR_BAD_FORMAT; /* end of string after separator */
+
field[nf] = str;
if (isdigit((unsigned char) *str))
{
{
case MONTH:
tm->tm_mon = val;
- haveTextMonth = TRUE;
+ haveTextMonth = true;
break;
default:
* Check valid year/month/day values, handle BC and DOY cases
* Return 0 if okay, a DTERR code if not.
*/
-static int
-ValidateDate(int fmask, bool is2digits, bool bc, struct pg_tm * tm)
+int
+ValidateDate(int fmask, bool isjulian, bool is2digits, bool bc,
+ struct pg_tm *tm)
{
if (fmask & DTK_M(YEAR))
{
- if (bc)
+ if (isjulian)
+ {
+ /* tm_year is correct and should not be touched */
+ }
+ else if (bc)
{
/* there is no year zero in AD/BC notation */
if (tm->tm_year <= 0)
*/
static int
DecodeTime(char *str, int fmask, int range,
- int *tmask, struct pg_tm * tm, fsec_t *fsec)
+ int *tmask, struct pg_tm *tm, fsec_t *fsec)
{
char *cp;
int dterr;
*tmask = DTK_TIME_M;
errno = 0;
- tm->tm_hour = strtoi(str, &cp, 10);
+ tm->tm_hour = strtoint(str, &cp, 10);
if (errno == ERANGE)
return DTERR_FIELD_OVERFLOW;
if (*cp != ':')
return DTERR_BAD_FORMAT;
errno = 0;
- tm->tm_min = strtoi(cp + 1, &cp, 10);
+ tm->tm_min = strtoint(cp + 1, &cp, 10);
if (errno == ERANGE)
return DTERR_FIELD_OVERFLOW;
if (*cp == '\0')
else if (*cp == ':')
{
errno = 0;
- tm->tm_sec = strtoi(cp + 1, &cp, 10);
+ tm->tm_sec = strtoint(cp + 1, &cp, 10);
if (errno == ERANGE)
return DTERR_FIELD_OVERFLOW;
if (*cp == '\0')
return DTERR_BAD_FORMAT;
/* do a sanity check */
-#ifdef HAVE_INT64_TIMESTAMP
- if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_min > 59 ||
- tm->tm_sec < 0 || tm->tm_sec > 60 || *fsec < INT64CONST(0) ||
+ if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_min > MINS_PER_HOUR - 1 ||
+ tm->tm_sec < 0 || tm->tm_sec > SECS_PER_MINUTE ||
+ *fsec < INT64CONST(0) ||
*fsec > USECS_PER_SEC)
return DTERR_FIELD_OVERFLOW;
-#else
- if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_min > 59 ||
- tm->tm_sec < 0 || tm->tm_sec > 60 || *fsec < 0 || *fsec > 1)
- return DTERR_FIELD_OVERFLOW;
-#endif
return 0;
}
*/
static int
DecodeNumber(int flen, char *str, bool haveTextMonth, int fmask,
- int *tmask, struct pg_tm * tm, fsec_t *fsec, bool *is2digits)
+ int *tmask, struct pg_tm *tm, fsec_t *fsec, bool *is2digits)
{
int val;
char *cp;
*tmask = 0;
errno = 0;
- val = strtoi(str, &cp, 10);
+ val = strtoint(str, &cp, 10);
if (errno == ERANGE)
return DTERR_FIELD_OVERFLOW;
if (cp == str)
/*
* Nothing so far; make a decision about what we think the input
- * is. There used to be lots of heuristics here, but the
+ * is. There used to be lots of heuristics here, but the
* consensus now is to be paranoid. It *must* be either
* YYYY-MM-DD (with a more-than-two-digit year field), or the
* field order defined by DateOrder.
{
/*
* We are at the first numeric field of a date that included a
- * textual month name. We want to support the variants
+ * textual month name. We want to support the variants
* MON-DD-YYYY, DD-MON-YYYY, and YYYY-MON-DD as unambiguous
- * inputs. We will also accept MON-DD-YY or DD-MON-YY in
+ * inputs. We will also accept MON-DD-YY or DD-MON-YY in
* either DMY or MDY modes, as well as YY-MON-DD in YMD mode.
*/
if (flen >= 3 || DateOrder == DATEORDER_YMD)
if (flen >= 3 && *is2digits)
{
/* Guess that first numeric field is day was wrong */
- *tmask = DTK_M(DAY); /* YEAR is already set */
+ *tmask = DTK_M(DAY); /* YEAR is already set */
tm->tm_mday = tm->tm_year;
tm->tm_year = val;
- *is2digits = FALSE;
+ *is2digits = false;
}
else
{
*/
static int
DecodeNumberField(int len, char *str, int fmask,
- int *tmask, struct pg_tm * tm, fsec_t *fsec, bool *is2digits)
+ int *tmask, struct pg_tm *tm, fsec_t *fsec, bool *is2digits)
{
char *cp;
frac = strtod(cp, NULL);
if (errno != 0)
return DTERR_BAD_FORMAT;
-#ifdef HAVE_INT64_TIMESTAMP
*fsec = rint(frac * 1000000);
-#else
- *fsec = frac;
-#endif
/* Now truncate off the fraction for further processing */
*cp = '\0';
len = strlen(str);
/* No decimal point and no complete date yet? */
else if ((fmask & DTK_DATE_M) != DTK_DATE_M)
{
- /* yyyymmdd? */
- if (len == 8)
+ if (len >= 6)
{
*tmask = DTK_DATE_M;
- tm->tm_mday = atoi(str + 6);
- *(str + 6) = '\0';
- tm->tm_mon = atoi(str + 4);
- *(str + 4) = '\0';
- tm->tm_year = atoi(str + 0);
-
- return DTK_DATE;
- }
- /* yymmdd? */
- else if (len == 6)
- {
- *tmask = DTK_DATE_M;
- tm->tm_mday = atoi(str + 4);
- *(str + 4) = '\0';
- tm->tm_mon = atoi(str + 2);
- *(str + 2) = '\0';
- tm->tm_year = atoi(str + 0);
- *is2digits = TRUE;
+ /*
+ * Start from end and consider first 2 as Day, next 2 as Month,
+ * and the rest as Year.
+ */
+ tm->tm_mday = atoi(str + (len - 2));
+ *(str + (len - 2)) = '\0';
+ tm->tm_mon = atoi(str + (len - 4));
+ *(str + (len - 4)) = '\0';
+ tm->tm_year = atoi(str);
+ if ((len - 4) == 2)
+ *is2digits = true;
return DTK_DATE;
}
*(str + 4) = '\0';
tm->tm_min = atoi(str + 2);
*(str + 2) = '\0';
- tm->tm_hour = atoi(str + 0);
+ tm->tm_hour = atoi(str);
return DTK_TIME;
}
tm->tm_sec = 0;
tm->tm_min = atoi(str + 2);
*(str + 2) = '\0';
- tm->tm_hour = atoi(str + 0);
+ tm->tm_hour = atoi(str);
return DTK_TIME;
}
* Interpret string as a numeric timezone.
*
* Return 0 if okay (and set *tzp), a DTERR code if not okay.
- *
- * NB: this must *not* ereport on failure; see commands/variable.c.
- *
- * Note: we allow timezone offsets up to 13:59. There are places that
- * use +1300 summer time.
*/
-static int
+int
DecodeTimezone(char *str, int *tzp)
{
int tz;
return DTERR_BAD_FORMAT;
errno = 0;
- hr = strtoi(str + 1, &cp, 10);
+ hr = strtoint(str + 1, &cp, 10);
if (errno == ERANGE)
return DTERR_TZDISP_OVERFLOW;
if (*cp == ':')
{
errno = 0;
- min = strtoi(cp + 1, &cp, 10);
+ min = strtoint(cp + 1, &cp, 10);
if (errno == ERANGE)
return DTERR_TZDISP_OVERFLOW;
if (*cp == ':')
{
errno = 0;
- sec = strtoi(cp + 1, &cp, 10);
+ sec = strtoint(cp + 1, &cp, 10);
if (errno == ERANGE)
return DTERR_TZDISP_OVERFLOW;
}
else
min = 0;
- if (hr < 0 || hr > 14)
+ /* Range-check the values; see notes in datatype/timestamp.h */
+ if (hr < 0 || hr > MAX_TZDISP_HOUR)
return DTERR_TZDISP_OVERFLOW;
- if (min < 0 || min >= 60)
+ if (min < 0 || min >= MINS_PER_HOUR)
return DTERR_TZDISP_OVERFLOW;
- if (sec < 0 || sec >= 60)
+ if (sec < 0 || sec >= SECS_PER_MINUTE)
return DTERR_TZDISP_OVERFLOW;
tz = (hr * MINS_PER_HOUR + min) * SECS_PER_MINUTE + sec;
return 0;
}
+
+/* DecodeTimezoneAbbrev()
+ * Interpret string as a timezone abbreviation, if possible.
+ *
+ * Returns 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.
+ *
+ * Given string must be lowercased already.
+ *
+ * Implement a cache lookup since it is likely that dates
+ * will be related in format.
+ */
+int
+DecodeTimezoneAbbrev(int field, char *lowtoken,
+ int *offset, pg_tz **tz)
+{
+ int type;
+ const datetkn *tp;
+
+ tp = abbrevcache[field];
+ /* use strncmp so that we match truncated tokens */
+ if (tp == NULL || strncmp(lowtoken, tp->token, TOKMAXLEN) != 0)
+ {
+ if (zoneabbrevtbl)
+ tp = datebsearch(lowtoken, zoneabbrevtbl->abbrevs,
+ zoneabbrevtbl->numabbrevs);
+ else
+ tp = NULL;
+ }
+ if (tp == NULL)
+ {
+ type = UNKNOWN_FIELD;
+ *offset = 0;
+ *tz = NULL;
+ }
+ else
+ {
+ abbrevcache[field] = tp;
+ type = tp->type;
+ if (type == DYNTZ)
+ {
+ *offset = 0;
+ *tz = FetchDynamicTimeZone(zoneabbrevtbl, tp);
+ }
+ else
+ {
+ *offset = tp->value;
+ *tz = NULL;
+ }
+ }
+
+ return type;
+}
+
+
/* DecodeSpecial()
* Decode text string using lookup table.
*
+ * Recognizes the keywords listed in datetktbl.
+ * Note: at one time this would also recognize timezone abbreviations,
+ * but no more; use DecodeTimezoneAbbrev for that.
+ *
+ * Given string must be lowercased already.
+ *
* Implement a cache lookup since it is likely that dates
* will be related in format.
- *
- * NB: this must *not* ereport on failure;
- * see commands/variable.c.
*/
int
DecodeSpecial(int field, char *lowtoken, int *val)
const datetkn *tp;
tp = datecache[field];
+ /* use strncmp so that we match truncated tokens */
if (tp == NULL || strncmp(lowtoken, tp->token, TOKMAXLEN) != 0)
{
- tp = datebsearch(lowtoken, timezonetktbl, sztimezonetktbl);
- if (tp == NULL)
- tp = datebsearch(lowtoken, datetktbl, szdatetktbl);
+ tp = datebsearch(lowtoken, datetktbl, szdatetktbl);
}
if (tp == NULL)
{
{
datecache[field] = tp;
type = tp->type;
- switch (type)
- {
- case TZ:
- case DTZ:
- case DTZMOD:
- *val = FROMVAL(tp);
- break;
-
- default:
- *val = tp->value;
- break;
- }
+ *val = tp->value;
}
return type;
}
-/* ClearPgTM
+/* ClearPgTm
*
* Zero out a pg_tm and associated fsec_t
*/
static inline void
-ClearPgTm(struct pg_tm * tm, fsec_t *fsec)
+ClearPgTm(struct pg_tm *tm, fsec_t *fsec)
{
tm->tm_year = 0;
tm->tm_mon = 0;
*/
int
DecodeInterval(char **field, int *ftype, int nf, int range,
- int *dtype, struct pg_tm * tm, fsec_t *fsec)
+ int *dtype, struct pg_tm *tm, fsec_t *fsec)
{
- bool is_before = FALSE;
+ bool is_before = false;
char *cp;
int fmask = 0,
tmask,
case DTK_TZ:
/*
- * Timezone is a token with a leading sign character and at
+ * Timezone means a token with a leading sign character and at
* least one digit; there could be ':', '.', '-' embedded in
* it as well.
*/
Assert(*field[i] == '-' || *field[i] == '+');
/*
- * Try for hh:mm or hh:mm:ss. If not, fall through to
- * DTK_NUMBER case, which can handle signed float numbers and
- * signed year-month values.
+ * Check for signed hh:mm or hh:mm:ss. If so, process exactly
+ * like DTK_TIME case above, plus handling the sign.
*/
if (strchr(field[i] + 1, ':') != NULL &&
- DecodeTime(field[i] + 1, fmask, INTERVAL_FULL_RANGE,
+ DecodeTime(field[i] + 1, fmask, range,
&tmask, tm, fsec) == 0)
{
if (*field[i] == '-')
* are reading right to left.
*/
type = DTK_DAY;
- tmask = DTK_M(TZ);
break;
}
- /* FALL THROUGH */
+
+ /*
+ * Otherwise, fall through to DTK_NUMBER case, which can
+ * handle signed float numbers and signed year-month values.
+ */
+
+ /* FALLTHROUGH */
case DTK_DATE:
case DTK_NUMBER:
}
errno = 0;
- val = strtoi(field[i], &cp, 10);
+ val = strtoint(field[i], &cp, 10);
if (errno == ERANGE)
return DTERR_FIELD_OVERFLOW;
/* SQL "years-months" syntax */
int val2;
- val2 = strtoi(cp + 1, &cp, 10);
+ val2 = strtoint(cp + 1, &cp, 10);
if (errno == ERANGE || val2 < 0 || val2 >= MONTHS_PER_YEAR)
return DTERR_FIELD_OVERFLOW;
if (*cp != '\0')
type = DTK_MONTH;
if (*field[i] == '-')
val2 = -val2;
+ if (((double) val * MONTHS_PER_YEAR + val2) > INT_MAX ||
+ ((double) val * MONTHS_PER_YEAR + val2) < INT_MIN)
+ return DTERR_FIELD_OVERFLOW;
val = val * MONTHS_PER_YEAR + val2;
fval = 0;
}
switch (type)
{
case DTK_MICROSEC:
-#ifdef HAVE_INT64_TIMESTAMP
*fsec += rint(val + fval);
-#else
- *fsec += (val + fval) * 1e-6;
-#endif
tmask = DTK_M(MICROSECOND);
break;
/* avoid overflowing the fsec field */
tm->tm_sec += val / 1000;
val -= (val / 1000) * 1000;
-#ifdef HAVE_INT64_TIMESTAMP
*fsec += rint((val + fval) * 1000);
-#else
- *fsec += (val + fval) * 1e-3;
-#endif
tmask = DTK_M(MILLISECOND);
break;
case DTK_SECOND:
tm->tm_sec += val;
-#ifdef HAVE_INT64_TIMESTAMP
*fsec += rint(fval * 1000000);
-#else
- *fsec += fval;
-#endif
/*
* If any subseconds were specified, consider this
break;
case AGO:
- is_before = TRUE;
+ is_before = true;
type = val;
break;
case RESERV:
- tmask = (DTK_DATE_M || DTK_TIME_M);
+ tmask = (DTK_DATE_M | DTK_TIME_M);
*dtype = val;
break;
{
int sec;
-#ifdef HAVE_INT64_TIMESTAMP
sec = *fsec / USECS_PER_SEC;
*fsec -= sec * USECS_PER_SEC;
-#else
- TMODULO(*fsec, sec, 1.0);
-#endif
tm->tm_sec += sec;
}
*/
int
DecodeISO8601Interval(char *str,
- int *dtype, struct pg_tm * tm, fsec_t *fsec)
+ int *dtype, struct pg_tm *tm, fsec_t *fsec)
{
bool datepart = true;
bool havefield = false;
return dterr;
/*
- * Note: we could step off the end of the string here. Code below
+ * Note: we could step off the end of the string here. Code below
* *must* exit the loop if unit == '\0'.
*/
unit = *str++;
{
case 'Y':
tm->tm_year += val;
- tm->tm_mon += (fval * 12);
+ tm->tm_mon += (fval * MONTHS_PER_YEAR);
break;
case 'M':
tm->tm_mon += val;
continue;
}
/* Else fall through to extended alternative format */
+ /* FALLTHROUGH */
case '-': /* ISO 8601 4.4.3.3 Alternative Format,
* Extended */
if (havefield)
return DTERR_BAD_FORMAT;
tm->tm_year += val;
- tm->tm_mon += (fval * 12);
+ tm->tm_mon += (fval * MONTHS_PER_YEAR);
if (unit == '\0')
return 0;
if (unit == 'T')
return 0;
}
/* Else fall through to extended alternative format */
+ /* FALLTHROUGH */
case ':': /* ISO 8601 4.4.3.3 Alternative Format,
* Extended */
if (havefield)
/* DecodeUnits()
* Decode text string using lookup table.
- * This routine supports time interval decoding
- * (hence, it need not recognize timezone names).
+ *
+ * This routine recognizes keywords associated with time interval units.
+ *
+ * Given string must be lowercased already.
+ *
+ * Implement a cache lookup since it is likely that dates
+ * will be related in format.
*/
int
DecodeUnits(int field, char *lowtoken, int *val)
const datetkn *tp;
tp = deltacache[field];
+ /* use strncmp so that we match truncated tokens */
if (tp == NULL || strncmp(lowtoken, tp->token, TOKMAXLEN) != 0)
{
tp = datebsearch(lowtoken, deltatktbl, szdeltatktbl);
{
deltacache[field] = tp;
type = tp->type;
- if (type == TZ || type == DTZ)
- *val = FROMVAL(tp);
- else
- *val = tp->value;
+ *val = tp->value;
}
return type;
-} /* DecodeUnits() */
+} /* DecodeUnits() */
/*
* Report an error detected by one of the datetime input processing routines.
(errcode(ERRCODE_DATETIME_FIELD_OVERFLOW),
errmsg("date/time field value out of range: \"%s\"",
str),
- errhint("Perhaps you need a different \"datestyle\" setting.")));
+ errhint("Perhaps you need a different \"datestyle\" setting.")));
break;
case DTERR_INTERVAL_OVERFLOW:
ereport(ERROR,
static const datetkn *
datebsearch(const char *key, const datetkn *base, int nel)
{
- const datetkn *last = base + nel - 1,
- *position;
- int result;
-
- while (last >= base)
+ if (nel > 0)
{
- position = base + ((last - base) >> 1);
- result = key[0] - position->token[0];
- if (result == 0)
+ const datetkn *last = base + nel - 1,
+ *position;
+ int result;
+
+ while (last >= base)
{
- result = strncmp(key, position->token, TOKMAXLEN);
+ position = base + ((last - base) >> 1);
+ /* precheck the first character for a bit of extra speed */
+ result = (int) key[0] - (int) position->token[0];
if (result == 0)
- return position;
+ {
+ /* use strncmp so that we match truncated tokens */
+ result = strncmp(key, position->token, TOKMAXLEN);
+ if (result == 0)
+ return position;
+ }
+ if (result < 0)
+ last = position - 1;
+ else
+ base = position + 1;
}
- if (result < 0)
- last = position - 1;
- else
- base = position + 1;
}
return NULL;
}
/* EncodeTimezone()
- * Append representation of a numeric timezone offset to str.
+ * Copies representation of a numeric timezone offset to str.
+ *
+ * Returns a pointer to the new end of string. No NUL terminator is put
+ * there; callers are responsible for NUL terminating str themselves.
*/
-static void
+static char *
EncodeTimezone(char *str, int tz, int style)
{
int hour,
hour = min / MINS_PER_HOUR;
min -= hour * MINS_PER_HOUR;
- str += strlen(str);
/* TZ is negated compared to sign we wish to display ... */
*str++ = (tz <= 0 ? '+' : '-');
if (sec != 0)
- sprintf(str, "%02d:%02d:%02d", hour, min, sec);
+ {
+ str = pg_ltostr_zeropad(str, hour, 2);
+ *str++ = ':';
+ str = pg_ltostr_zeropad(str, min, 2);
+ *str++ = ':';
+ str = pg_ltostr_zeropad(str, sec, 2);
+ }
else if (min != 0 || style == USE_XSD_DATES)
- sprintf(str, "%02d:%02d", hour, min);
+ {
+ str = pg_ltostr_zeropad(str, hour, 2);
+ *str++ = ':';
+ str = pg_ltostr_zeropad(str, min, 2);
+ }
else
- sprintf(str, "%02d", hour);
+ str = pg_ltostr_zeropad(str, hour, 2);
+ return str;
}
/* EncodeDateOnly()
* Encode date as local time.
*/
void
-EncodeDateOnly(struct pg_tm * tm, int style, char *str)
+EncodeDateOnly(struct pg_tm *tm, int style, char *str)
{
Assert(tm->tm_mon >= 1 && tm->tm_mon <= MONTHS_PER_YEAR);
case USE_ISO_DATES:
case USE_XSD_DATES:
/* compatible with ISO date formats */
- if (tm->tm_year > 0)
- sprintf(str, "%04d-%02d-%02d",
- tm->tm_year, tm->tm_mon, tm->tm_mday);
- else
- sprintf(str, "%04d-%02d-%02d %s",
- -(tm->tm_year - 1), tm->tm_mon, tm->tm_mday, "BC");
+ str = pg_ltostr_zeropad(str,
+ (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
+ *str++ = '-';
+ str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
+ *str++ = '-';
+ str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
break;
case USE_SQL_DATES:
/* compatible with Oracle/Ingres date formats */
if (DateOrder == DATEORDER_DMY)
- sprintf(str, "%02d/%02d", tm->tm_mday, tm->tm_mon);
- else
- sprintf(str, "%02d/%02d", tm->tm_mon, tm->tm_mday);
- if (tm->tm_year > 0)
- sprintf(str + 5, "/%04d", tm->tm_year);
+ {
+ str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+ *str++ = '/';
+ str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
+ }
else
- sprintf(str + 5, "/%04d %s", -(tm->tm_year - 1), "BC");
+ {
+ str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
+ *str++ = '/';
+ str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+ }
+ *str++ = '/';
+ str = pg_ltostr_zeropad(str,
+ (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
break;
case USE_GERMAN_DATES:
/* German-style date format */
- sprintf(str, "%02d.%02d", tm->tm_mday, tm->tm_mon);
- if (tm->tm_year > 0)
- sprintf(str + 5, ".%04d", tm->tm_year);
- else
- sprintf(str + 5, ".%04d %s", -(tm->tm_year - 1), "BC");
+ str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+ *str++ = '.';
+ str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
+ *str++ = '.';
+ str = pg_ltostr_zeropad(str,
+ (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
break;
case USE_POSTGRES_DATES:
default:
/* traditional date-only style for Postgres */
if (DateOrder == DATEORDER_DMY)
- sprintf(str, "%02d-%02d", tm->tm_mday, tm->tm_mon);
- else
- sprintf(str, "%02d-%02d", tm->tm_mon, tm->tm_mday);
- if (tm->tm_year > 0)
- sprintf(str + 5, "-%04d", tm->tm_year);
+ {
+ str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+ *str++ = '-';
+ str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
+ }
else
- sprintf(str + 5, "-%04d %s", -(tm->tm_year - 1), "BC");
+ {
+ str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
+ *str++ = '-';
+ str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+ }
+ *str++ = '-';
+ str = pg_ltostr_zeropad(str,
+ (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
break;
}
+
+ if (tm->tm_year <= 0)
+ {
+ memcpy(str, " BC", 3); /* Don't copy NUL */
+ str += 3;
+ }
+ *str = '\0';
}
/* EncodeTimeOnly()
* Encode time fields only.
+ *
+ * tm and fsec are the value to encode, print_tz determines whether to include
+ * a time zone (the difference between time and timetz types), tz is the
+ * numeric time zone offset, style is the date style, str is where to write the
+ * output.
*/
void
-EncodeTimeOnly(struct pg_tm * tm, fsec_t fsec, int *tzp, int style, char *str)
+EncodeTimeOnly(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, int style, char *str)
{
- sprintf(str, "%02d:%02d:", tm->tm_hour, tm->tm_min);
- str += strlen(str);
-
- AppendSeconds(str, tm->tm_sec, fsec, MAX_TIME_PRECISION, true);
-
- if (tzp != NULL)
- EncodeTimezone(str, *tzp, style);
+ str = pg_ltostr_zeropad(str, tm->tm_hour, 2);
+ *str++ = ':';
+ str = pg_ltostr_zeropad(str, tm->tm_min, 2);
+ *str++ = ':';
+ str = AppendSeconds(str, tm->tm_sec, fsec, MAX_TIME_PRECISION, true);
+ if (print_tz)
+ str = EncodeTimezone(str, tz, style);
+ *str = '\0';
}
/* EncodeDateTime()
* Encode date and time interpreted as local time.
- * Support several date styles:
+ *
+ * tm and fsec are the value to encode, print_tz determines whether to include
+ * a time zone (the difference between timestamp and timestamptz types), tz is
+ * the numeric time zone offset, tzn is the textual time zone, which if
+ * specified will be used instead of tz by some styles, style is the date
+ * style, str is where to write the output.
+ *
+ * Supported date styles:
* Postgres - day mon hh:mm:ss yyyy tz
* SQL - mm/dd/yyyy hh:mm:ss.ss tz
* ISO - yyyy-mm-dd hh:mm:ss+/-tz
* German - dd.mm.yyyy hh:mm:ss tz
* XSD - yyyy-mm-ddThh:mm:ss.ss+/-tz
- * Variants (affects order of month and day for Postgres and SQL styles):
- * US - mm/dd/yyyy
- * European - dd/mm/yyyy
*/
void
-EncodeDateTime(struct pg_tm * tm, fsec_t fsec, int *tzp, char **tzn, int style, char *str)
+EncodeDateTime(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, const char *tzn, int style, char *str)
{
int day;
Assert(tm->tm_mon >= 1 && tm->tm_mon <= MONTHS_PER_YEAR);
+ /*
+ * Negative tm_isdst means we have no valid time zone translation.
+ */
+ if (tm->tm_isdst < 0)
+ print_tz = false;
+
switch (style)
{
case USE_ISO_DATES:
case USE_XSD_DATES:
/* Compatible with ISO-8601 date formats */
-
- if (style == USE_ISO_DATES)
- sprintf(str, "%04d-%02d-%02d %02d:%02d:",
- (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1),
- tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min);
- else
- sprintf(str, "%04d-%02d-%02dT%02d:%02d:",
- (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1),
- tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min);
-
- AppendTimestampSeconds(str + strlen(str), tm, fsec);
-
- /*
- * tzp == NULL indicates that we don't want *any* time zone info
- * in the output string. *tzn != NULL indicates that we have alpha
- * time zone info available. tm_isdst != -1 indicates that we have
- * a valid time zone translation.
- */
- if (tzp != NULL && tm->tm_isdst >= 0)
- EncodeTimezone(str, *tzp, style);
-
- if (tm->tm_year <= 0)
- sprintf(str + strlen(str), " BC");
+ str = pg_ltostr_zeropad(str,
+ (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
+ *str++ = '-';
+ str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
+ *str++ = '-';
+ str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+ *str++ = (style == USE_ISO_DATES) ? ' ' : 'T';
+ str = pg_ltostr_zeropad(str, tm->tm_hour, 2);
+ *str++ = ':';
+ str = pg_ltostr_zeropad(str, tm->tm_min, 2);
+ *str++ = ':';
+ str = AppendTimestampSeconds(str, tm, fsec);
+ if (print_tz)
+ str = EncodeTimezone(str, tz, style);
break;
case USE_SQL_DATES:
/* Compatible with Oracle/Ingres date formats */
-
if (DateOrder == DATEORDER_DMY)
- sprintf(str, "%02d/%02d", tm->tm_mday, tm->tm_mon);
+ {
+ str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+ *str++ = '/';
+ str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
+ }
else
- sprintf(str, "%02d/%02d", tm->tm_mon, tm->tm_mday);
-
- sprintf(str + 5, "/%04d %02d:%02d:",
- (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1),
- tm->tm_hour, tm->tm_min);
-
- AppendTimestampSeconds(str + strlen(str), tm, fsec);
+ {
+ str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
+ *str++ = '/';
+ str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+ }
+ *str++ = '/';
+ str = pg_ltostr_zeropad(str,
+ (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
+ *str++ = ' ';
+ str = pg_ltostr_zeropad(str, tm->tm_hour, 2);
+ *str++ = ':';
+ str = pg_ltostr_zeropad(str, tm->tm_min, 2);
+ *str++ = ':';
+ str = AppendTimestampSeconds(str, tm, fsec);
/*
* Note: the uses of %.*s in this function would be risky if the
* timezone names ever contain non-ASCII characters. However, all
- * TZ abbreviations in the Olson database are plain ASCII.
+ * TZ abbreviations in the IANA database are plain ASCII.
*/
-
- if (tzp != NULL && tm->tm_isdst >= 0)
+ if (print_tz)
{
- if (*tzn != NULL)
- sprintf(str + strlen(str), " %.*s", MAXTZLEN, *tzn);
+ if (tzn)
+ {
+ sprintf(str, " %.*s", MAXTZLEN, tzn);
+ str += strlen(str);
+ }
else
- EncodeTimezone(str, *tzp, style);
+ str = EncodeTimezone(str, tz, style);
}
-
- if (tm->tm_year <= 0)
- sprintf(str + strlen(str), " BC");
break;
case USE_GERMAN_DATES:
/* German variant on European style */
-
- sprintf(str, "%02d.%02d", tm->tm_mday, tm->tm_mon);
-
- sprintf(str + 5, ".%04d %02d:%02d:",
- (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1),
- tm->tm_hour, tm->tm_min);
-
- AppendTimestampSeconds(str + strlen(str), tm, fsec);
-
- if (tzp != NULL && tm->tm_isdst >= 0)
+ str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+ *str++ = '.';
+ str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
+ *str++ = '.';
+ str = pg_ltostr_zeropad(str,
+ (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
+ *str++ = ' ';
+ str = pg_ltostr_zeropad(str, tm->tm_hour, 2);
+ *str++ = ':';
+ str = pg_ltostr_zeropad(str, tm->tm_min, 2);
+ *str++ = ':';
+ str = AppendTimestampSeconds(str, tm, fsec);
+
+ if (print_tz)
{
- if (*tzn != NULL)
- sprintf(str + strlen(str), " %.*s", MAXTZLEN, *tzn);
+ if (tzn)
+ {
+ sprintf(str, " %.*s", MAXTZLEN, tzn);
+ str += strlen(str);
+ }
else
- EncodeTimezone(str, *tzp, style);
+ str = EncodeTimezone(str, tz, style);
}
-
- if (tm->tm_year <= 0)
- sprintf(str + strlen(str), " BC");
break;
case USE_POSTGRES_DATES:
default:
/* Backward-compatible with traditional Postgres abstime dates */
-
day = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday);
tm->tm_wday = j2day(day);
-
- strncpy(str, days[tm->tm_wday], 3);
- strcpy(str + 3, " ");
-
+ memcpy(str, days[tm->tm_wday], 3);
+ str += 3;
+ *str++ = ' ';
if (DateOrder == DATEORDER_DMY)
- sprintf(str + 4, "%02d %3s", tm->tm_mday, months[tm->tm_mon - 1]);
+ {
+ str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+ *str++ = ' ';
+ memcpy(str, months[tm->tm_mon - 1], 3);
+ str += 3;
+ }
else
- sprintf(str + 4, "%3s %02d", months[tm->tm_mon - 1], tm->tm_mday);
-
- sprintf(str + 10, " %02d:%02d:", tm->tm_hour, tm->tm_min);
-
- AppendTimestampSeconds(str + strlen(str), tm, fsec);
-
- sprintf(str + strlen(str), " %04d",
- (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1));
-
- if (tzp != NULL && tm->tm_isdst >= 0)
{
- if (*tzn != NULL)
- sprintf(str + strlen(str), " %.*s", MAXTZLEN, *tzn);
+ memcpy(str, months[tm->tm_mon - 1], 3);
+ str += 3;
+ *str++ = ' ';
+ str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+ }
+ *str++ = ' ';
+ str = pg_ltostr_zeropad(str, tm->tm_hour, 2);
+ *str++ = ':';
+ str = pg_ltostr_zeropad(str, tm->tm_min, 2);
+ *str++ = ':';
+ str = AppendTimestampSeconds(str, tm, fsec);
+ *str++ = ' ';
+ str = pg_ltostr_zeropad(str,
+ (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
+
+ if (print_tz)
+ {
+ if (tzn)
+ {
+ sprintf(str, " %.*s", MAXTZLEN, tzn);
+ str += strlen(str);
+ }
else
{
/*
* avoid formatting something which would be rejected by
* the date/time parser later. - thomas 2001-10-19
*/
- sprintf(str + strlen(str), " ");
- EncodeTimezone(str, *tzp, style);
+ *str++ = ' ';
+ str = EncodeTimezone(str, tz, style);
}
}
-
- if (tm->tm_year <= 0)
- sprintf(str + strlen(str), " BC");
break;
}
+
+ if (tm->tm_year <= 0)
+ {
+ memcpy(str, " BC", 3); /* Don't copy NUL */
+ str += 3;
+ }
+ *str = '\0';
}
* tad bizarre but it's how it worked before...
*/
*is_before = (value < 0);
- *is_zero = FALSE;
+ *is_zero = false;
return cp + strlen(cp);
}
else if (*is_before)
value = -value;
sprintf(cp, " %d %s%s", value, units, (value == 1) ? "" : "s");
- *is_zero = FALSE;
+ *is_zero = false;
return cp + strlen(cp);
}
* "day-time literal"s (that look like ('4 5:6:7')
*/
void
-EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str)
+EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
{
char *cp = str;
int year = tm->tm_year;
int hour = tm->tm_hour;
int min = tm->tm_min;
int sec = tm->tm_sec;
- bool is_before = FALSE;
- bool is_zero = TRUE;
+ bool is_before = false;
+ bool is_zero = true;
/*
* The sign of year and month are guaranteed to match, since they are
day_sign, abs(mday),
sec_sign, abs(hour), abs(min));
cp += strlen(cp);
- AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+ cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+ *cp = '\0';
}
else if (has_year_month)
{
{
sprintf(cp, "%d %d:%02d:", mday, hour, min);
cp += strlen(cp);
- AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+ cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+ *cp = '\0';
}
else
{
sprintf(cp, "%d:%02d:", hour, min);
cp += strlen(cp);
- AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+ cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+ *cp = '\0';
}
}
break;
{
if (sec < 0 || fsec < 0)
*cp++ = '-';
- AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
- cp += strlen(cp);
+ cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
*cp++ = 'S';
*cp++ = '\0';
}
/* Compatible with postgresql < 8.4 when DateStyle = 'iso' */
case INTSTYLE_POSTGRES:
cp = AddPostgresIntPart(cp, year, "year", &is_zero, &is_before);
+
+ /*
+ * Ideally we should spell out "month" like we do for "year" and
+ * "day". However, for backward compatibility, we can't easily
+ * fix this. bjm 2011-05-24
+ */
cp = AddPostgresIntPart(cp, mon, "mon", &is_zero, &is_before);
cp = AddPostgresIntPart(cp, mday, "day", &is_zero, &is_before);
if (is_zero || hour != 0 || min != 0 || sec != 0 || fsec != 0)
(minus ? "-" : (is_before ? "+" : "")),
abs(hour), abs(min));
cp += strlen(cp);
- AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+ cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+ *cp = '\0';
}
break;
if (sec < 0 || (sec == 0 && fsec < 0))
{
if (is_zero)
- is_before = TRUE;
+ is_before = true;
else if (!is_before)
*cp++ = '-';
}
else if (is_before)
*cp++ = '-';
- AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
- cp += strlen(cp);
+ cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
sprintf(cp, " sec%s",
(abs(sec) != 1 || fsec != 0) ? "s" : "");
- is_zero = FALSE;
+ is_zero = false;
}
/* identically zero? then put in a unitless zero... */
if (is_zero)
/*
* We've been burnt by stupid errors in the ordering of the datetkn tables
- * once too often. Arrange to check them during postmaster start.
+ * once too often. Arrange to check them during postmaster start.
*/
static bool
CheckDateTokenTable(const char *tablename, const datetkn *base, int nel)
bool ok = true;
int i;
- for (i = 1; i < nel; i++)
+ for (i = 0; i < nel; i++)
{
- if (strncmp(base[i - 1].token, base[i].token, TOKMAXLEN) >= 0)
+ /* check for token strings that don't fit */
+ if (strlen(base[i].token) > TOKMAXLEN)
{
/* %.*s is safe since all our tokens are ASCII */
- elog(LOG, "ordering error in %s table: \"%.*s\" >= \"%.*s\"",
+ elog(LOG, "token too long in %s table: \"%.*s\"",
+ tablename,
+ TOKMAXLEN + 1, base[i].token);
+ ok = false;
+ break; /* don't risk applying strcmp */
+ }
+ /* check for out of order */
+ if (i > 0 &&
+ strcmp(base[i - 1].token, base[i].token) >= 0)
+ {
+ elog(LOG, "ordering error in %s table: \"%s\" >= \"%s\"",
tablename,
- TOKMAXLEN, base[i - 1].token,
- TOKMAXLEN, base[i].token);
+ base[i - 1].token,
+ base[i].token);
ok = false;
}
}
return ok;
}
+/*
+ * Common code for temporal prosupport functions: simplify, if possible,
+ * a call to a temporal type's length-coercion function.
+ *
+ * Types time, timetz, timestamp and timestamptz each have a range of allowed
+ * precisions. An unspecified precision is rigorously equivalent to the
+ * highest specifiable precision. We can replace the function call with a
+ * no-op RelabelType if it is coercing to the same or higher precision as the
+ * input is known to have.
+ *
+ * The input Node is always a FuncExpr, but to reduce the #include footprint
+ * of datetime.h, we declare it as Node *.
+ *
+ * Note: timestamp_scale throws an error when the typmod is out of range, but
+ * we can't get there from a cast: our typmodin will have caught it already.
+ */
+Node *
+TemporalSimplify(int32 max_precis, Node *node)
+{
+ FuncExpr *expr = castNode(FuncExpr, node);
+ Node *ret = NULL;
+ Node *typmod;
+
+ Assert(list_length(expr->args) >= 2);
+
+ typmod = (Node *) lsecond(expr->args);
+
+ if (IsA(typmod, Const) &&!((Const *) typmod)->constisnull)
+ {
+ Node *source = (Node *) linitial(expr->args);
+ int32 old_precis = exprTypmod(source);
+ int32 new_precis = DatumGetInt32(((Const *) typmod)->constvalue);
+
+ if (new_precis < 0 || new_precis == max_precis ||
+ (old_precis >= 0 && new_precis >= old_precis))
+ ret = relabel_to_typmod(source, new_precis);
+ }
+
+ return ret;
+}
+
/*
* This function gets called during timezone config file load or reload
* to create the final array of timezone tokens. The argument array
- * is already sorted in name order. This data is in a temporary memory
- * context and must be copied to somewhere permanent.
+ * is already sorted in name order.
+ *
+ * The result is a TimeZoneAbbrevTable (which must be a single malloc'd chunk)
+ * or NULL on malloc failure. No other error conditions are defined.
*/
-void
-InstallTimeZoneAbbrevs(tzEntry *abbrevs, int n)
+TimeZoneAbbrevTable *
+ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs, int n)
{
- datetkn *newtbl;
+ TimeZoneAbbrevTable *tbl;
+ Size tbl_size;
int i;
- /*
- * Copy the data into TopMemoryContext and convert to datetkn format.
- */
- newtbl = (datetkn *) MemoryContextAlloc(TopMemoryContext,
- n * sizeof(datetkn));
+ /* Space for fixed fields and datetkn array */
+ tbl_size = offsetof(TimeZoneAbbrevTable, abbrevs) +
+ n * sizeof(datetkn);
+ tbl_size = MAXALIGN(tbl_size);
+ /* Count up space for dynamic abbreviations */
for (i = 0; i < n; i++)
{
- strncpy(newtbl[i].token, abbrevs[i].abbrev, TOKMAXLEN);
- newtbl[i].type = abbrevs[i].is_dst ? DTZ : TZ;
- TOVAL(&newtbl[i], abbrevs[i].offset / 60);
+ struct tzEntry *abbr = abbrevs + i;
+
+ if (abbr->zone != NULL)
+ {
+ Size dsize;
+
+ dsize = offsetof(DynamicZoneAbbrev, zone) +
+ strlen(abbr->zone) + 1;
+ tbl_size += MAXALIGN(dsize);
+ }
}
+ /* Alloc the result ... */
+ tbl = malloc(tbl_size);
+ if (!tbl)
+ return NULL;
+
+ /* ... and fill it in */
+ tbl->tblsize = tbl_size;
+ tbl->numabbrevs = n;
+ /* in this loop, tbl_size reprises the space calculation above */
+ tbl_size = offsetof(TimeZoneAbbrevTable, abbrevs) +
+ n * sizeof(datetkn);
+ tbl_size = MAXALIGN(tbl_size);
+ for (i = 0; i < n; i++)
+ {
+ struct tzEntry *abbr = abbrevs + i;
+ datetkn *dtoken = tbl->abbrevs + i;
+
+ /* use strlcpy to truncate name if necessary */
+ strlcpy(dtoken->token, abbr->abbrev, TOKMAXLEN + 1);
+ if (abbr->zone != NULL)
+ {
+ /* Allocate a DynamicZoneAbbrev for this abbreviation */
+ DynamicZoneAbbrev *dtza;
+ Size dsize;
+
+ dtza = (DynamicZoneAbbrev *) ((char *) tbl + tbl_size);
+ dtza->tz = NULL;
+ strcpy(dtza->zone, abbr->zone);
+
+ dtoken->type = DYNTZ;
+ /* value is offset from table start to DynamicZoneAbbrev */
+ dtoken->value = (int32) tbl_size;
+
+ dsize = offsetof(DynamicZoneAbbrev, zone) +
+ strlen(abbr->zone) + 1;
+ tbl_size += MAXALIGN(dsize);
+ }
+ else
+ {
+ dtoken->type = abbr->is_dst ? DTZ : TZ;
+ dtoken->value = abbr->offset;
+ }
+ }
+
+ /* Assert the two loops above agreed on size calculations */
+ Assert(tbl->tblsize == tbl_size);
+
/* Check the ordering, if testing */
- Assert(CheckDateTokenTable("timezone offset", newtbl, n));
+ Assert(CheckDateTokenTable("timezone abbreviations", tbl->abbrevs, n));
+
+ return tbl;
+}
+
+/*
+ * Install a TimeZoneAbbrevTable as the active table.
+ *
+ * Caller is responsible that the passed table doesn't go away while in use.
+ */
+void
+InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl)
+{
+ zoneabbrevtbl = tbl;
+ /* reset abbrevcache, which may contain pointers into old table */
+ memset(abbrevcache, 0, sizeof(abbrevcache));
+}
+
+/*
+ * Helper subroutine to locate pg_tz timezone for a dynamic abbreviation.
+ */
+static pg_tz *
+FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp)
+{
+ DynamicZoneAbbrev *dtza;
+
+ /* Just some sanity checks to prevent indexing off into nowhere */
+ Assert(tp->type == DYNTZ);
+ Assert(tp->value > 0 && tp->value < tbl->tblsize);
- /* Now safe to replace existing table (if any) */
- if (timezonetktbl)
- pfree(timezonetktbl);
- timezonetktbl = newtbl;
- sztimezonetktbl = n;
+ dtza = (DynamicZoneAbbrev *) ((char *) tbl + tp->value);
- /* clear date cache in case it contains any stale timezone names */
- for (i = 0; i < MAXDATEFIELDS; i++)
- datecache[i] = NULL;
+ /* Look up the underlying zone if we haven't already */
+ 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)));
+ }
+ return dtza->tz;
}
+
/*
* This set-returning function reads all the available time zone abbreviations
* and returns a set of (abbrev, utc_offset, is_dst).
HeapTuple tuple;
Datum values[3];
bool nulls[3];
+ const datetkn *tp;
char buffer[TOKMAXLEN + 1];
+ int gmtoffset;
+ bool is_dst;
unsigned char *p;
struct pg_tm tm;
Interval *resInterval;
* build tupdesc for result tuples. This must match this function's
* pg_proc entry!
*/
- tupdesc = CreateTemplateTupleDesc(3, false);
+ tupdesc = CreateTemplateTupleDesc(3);
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "abbrev",
TEXTOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "utc_offset",
funcctx = SRF_PERCALL_SETUP();
pindex = (int *) funcctx->user_fctx;
- if (*pindex >= sztimezonetktbl)
+ if (zoneabbrevtbl == NULL ||
+ *pindex >= zoneabbrevtbl->numabbrevs)
SRF_RETURN_DONE(funcctx);
+ tp = zoneabbrevtbl->abbrevs + *pindex;
+
+ switch (tp->type)
+ {
+ case TZ:
+ gmtoffset = tp->value;
+ is_dst = false;
+ break;
+ case DTZ:
+ gmtoffset = tp->value;
+ is_dst = true;
+ break;
+ case DYNTZ:
+ {
+ /* Determine the current meaning of the abbrev */
+ pg_tz *tzp;
+ TimestampTz now;
+ int isdst;
+
+ tzp = FetchDynamicTimeZone(zoneabbrevtbl, tp);
+ now = GetCurrentTransactionStartTimestamp();
+ gmtoffset = -DetermineTimeZoneAbbrevOffsetTS(now,
+ tp->token,
+ tzp,
+ &isdst);
+ is_dst = (bool) isdst;
+ break;
+ }
+ default:
+ elog(ERROR, "unrecognized timezone type %d", (int) tp->type);
+ gmtoffset = 0; /* keep compiler quiet */
+ is_dst = false;
+ break;
+ }
+
MemSet(nulls, 0, sizeof(nulls));
/*
* Convert name to text, using upcasing conversion that is the inverse of
* what ParseDateTime() uses.
*/
- strncpy(buffer, timezonetktbl[*pindex].token, TOKMAXLEN);
- buffer[TOKMAXLEN] = '\0'; /* may not be null-terminated */
+ strlcpy(buffer, tp->token, sizeof(buffer));
for (p = (unsigned char *) buffer; *p; p++)
*p = pg_toupper(*p);
values[0] = CStringGetTextDatum(buffer);
+ /* Convert offset (in seconds) to an interval */
MemSet(&tm, 0, sizeof(struct pg_tm));
- tm.tm_min = (-1) * FROMVAL(&timezonetktbl[*pindex]);
+ tm.tm_sec = gmtoffset;
resInterval = (Interval *) palloc(sizeof(Interval));
tm2interval(&tm, 0, resInterval);
values[1] = IntervalPGetDatum(resInterval);
- Assert(timezonetktbl[*pindex].type == DTZ ||
- timezonetktbl[*pindex].type == TZ);
- values[2] = BoolGetDatum(timezonetktbl[*pindex].type == DTZ);
+ values[2] = BoolGetDatum(is_dst);
(*pindex)++;
int tzoff;
struct pg_tm tm;
fsec_t fsec;
- char *tzn;
+ const char *tzn;
Interval *resInterval;
struct pg_tm itm;
* build tupdesc for result tuples. This must match this function's
* pg_proc entry!
*/
- tupdesc = CreateTemplateTupleDesc(4, false);
+ tupdesc = CreateTemplateTupleDesc(4);
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name",
TEXTOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "abbrev",
&tzoff, &tm, &fsec, &tzn, tz) != 0)
continue; /* ignore if conversion fails */
- /* Ignore zic's rather silly "Factory" time zone */
- if (tzn && strcmp(tzn, "Local time zone must be set--see zic manual page") == 0)
+ /*
+ * IANA's rather silly "Factory" time zone used to emit ridiculously
+ * long "abbreviations" such as "Local time zone must be set--see zic
+ * manual page" or "Local time zone must be set--use tzsetup". While
+ * modern versions of tzdb emit the much saner "-00", it seems some
+ * benighted packagers are hacking the IANA data so that it continues
+ * to produce these strings. To prevent producing a weirdly wide
+ * abbrev column, reject ridiculously long abbreviations.
+ */
+ if (tzn && strlen(tzn) > 31)
continue;
/* Found a displayable zone */