/* *j2day-convertJuliandatetoday-of-week(0..6==Sun..Sat) * *Note:variousplacesusethelocutionj2day(date-1)toproducea *resultaccordingtotheconvention0..6=Mon..Sun.Thisisabitof *acrock,butwillworkaslongasthecomputationhereisjustamodulo.
*/ int
j2day(int date)
{
date += 1;
date %= 7; /* Cope if division truncates towards zero, as it probably does */ if (date < 0)
date += 7;
/* outer loop through fields */ while (*cp != '\0')
{ /* Ignore spaces between fields */ if (isspace((unsignedchar) *cp))
{
cp++; continue;
}
/* Record start of current field */ if (nf >= maxfields) return DTERR_BAD_FORMAT;
field[nf] = bufp;
/* leading digit? then date or time */ if (isdigit((unsignedchar) *cp))
{
APPEND_CHAR(bufp, bufend, *cp++); while (isdigit((unsignedchar) *cp))
APPEND_CHAR(bufp, bufend, *cp++);
/* time field? */ if (*cp == ':')
{
ftype[nf] = DTK_TIME;
APPEND_CHAR(bufp, bufend, *cp++); while (isdigit((unsignedchar) *cp) ||
(*cp == ':') || (*cp == '.'))
APPEND_CHAR(bufp, bufend, *cp++);
} /* date field? allow embedded text month */ elseif (*cp == '-' || *cp == '/' || *cp == '.')
{ /* save delimiting character to use later */ char delim = *cp;
APPEND_CHAR(bufp, bufend, *cp++); /* second field is all digits? then no embedded text month */ if (isdigit((unsignedchar) *cp))
{
ftype[nf] = ((delim == '.') ? DTK_NUMBER : DTK_DATE); while (isdigit((unsignedchar) *cp))
APPEND_CHAR(bufp, bufend, *cp++);
/* *Alreadyhaveadate?Thenthismightbeatimezonename *withembeddedpunctuation(e.g."America/New_York")ora *run-togethertimewithtrailingtimezone(e.g.hhmmss-zz). *-thomas2001-12-25 * *Weconsideritatimezoneifwealreadyhavemonth&day. *Thisistoallowtheform"mmmddhhmmsstzyear",which *we'vehistoricallyaccepted.
*/ elseif (ptype != 0 ||
((fmask & (DTK_M(MONTH) | DTK_M(DAY))) ==
(DTK_M(MONTH) | DTK_M(DAY))))
{ /* No time zone accepted? Then quit... */ if (tzp == NULL) return DTERR_BAD_FORMAT;
if (isdigit((unsignedchar) *field[i]) || ptype != 0)
{ char *cp;
/* *Allowapreceding"t"field,butnootherunits.
*/ if (ptype != 0)
{ /* Sanity check; should not fail this test */ if (ptype != DTK_TIME) return DTERR_BAD_FORMAT;
ptype = 0;
}
case DTK_TIME: /* previous field was "t" for ISO time */
dterr = DecodeNumberField(strlen(field[i]), field[i],
(fmask | DTK_DATE_M),
&tmask, tm,
fsec, &is2digits); if (dterr < 0) return dterr; if (tmask != DTK_TIME_M) return DTERR_BAD_FORMAT; break;
case DTK_STRING: case DTK_SPECIAL: /* timezone abbrevs take precedence over built-in tokens */
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) continue;
/* No preceding date? Then quit... */ if ((fmask & DTK_DATE_M) != DTK_DATE_M) return DTERR_BAD_FORMAT;
/* reject consecutive unhandled units */ if (ptype != 0) return DTERR_BAD_FORMAT;
ptype = val; break;
case UNKNOWN_FIELD:
/* *Beforegivingupanddeclaringerror,checktosee *ifitisanall-alphatimezonename.
*/
namedTz = pg_tzset(field[i]); if (!namedTz) return DTERR_BAD_FORMAT; /* we'll apply the zone setting below */
tmask = DTK_M(TZ); break;
default: return DTERR_BAD_FORMAT;
} break;
default: return DTERR_BAD_FORMAT;
}
if (tmask & fmask) return DTERR_BAD_FORMAT;
fmask |= tmask;
} /* end loop over fields */
/* reject if prefix type appeared and was never handled */ if (ptype != 0) return DTERR_BAD_FORMAT;
/* do additional checking for normal date specs (but not "infinity" etc) */ if (*dtype == DTK_DATE)
{ /* do final checking/adjustment of Y/M/D fields */
dterr = ValidateDate(fmask, isjulian, is2digits, bc, tm); if (dterr) return dterr;
/* check for incomplete input */ if ((fmask & DTK_DATE_M) != DTK_DATE_M)
{ if ((fmask & DTK_TIME_M) == DTK_TIME_M) return1; return DTERR_BAD_FORMAT;
}
/* *Ifwehadafulltimezonespec,computetheoffset(wecouldnotdo *itbefore,becauseweneedthedatetoresolveDSTstatus).
*/ if (namedTz != NULL)
{ /* daylight savings time modifier disallowed with full TZ */ if (fmask & DTK_M(DTZMOD)) return DTERR_BAD_FORMAT;
*tzp = DetermineTimeZoneOffset(tm, namedTz);
}
/* *Likewise,ifwehadadynamictimezoneabbreviation,resolveit *now.
*/ if (abbrevTz != NULL)
{ /* daylight savings time modifier disallowed with dynamic TZ */ if (fmask & DTK_M(DTZMOD)) return DTERR_BAD_FORMAT;
/* *First,generatethepg_time_tvaluecorrespondingtothegiven *y/m/d/h/m/stakenasGMTtime.Ifthisoverflows,puntanddecidethe *timezoneisGMT.(ForavalidJuliandate,integeroverflowshouldbe *impossiblewith64-bitpg_time_t,butlet'scheckforsafety.)
*/ if (!IS_VALID_JULIAN(tm->tm_year, tm->tm_mon, tm->tm_mday)) goto overflow;
date = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - UNIX_EPOCH_JDATE;
day = ((pg_time_t) date) * SECS_PER_DAY; if (day / SECS_PER_DAY != date) goto overflow;
sec = tm->tm_sec + (tm->tm_min + tm->tm_hour * MINS_PER_HOUR) * SECS_PER_MINUTE;
mytime = day + sec; /* since sec >= 0, overflow could only be from +day to -mytime */ if (mytime < 0 && day > 0) goto overflow;
/* We need to force the abbrev to upper case */
strlcpy(upabbr, abbr, sizeof(upabbr)); for (p = (unsignedchar *) 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; returntrue;
} returnfalse;
}
/* We need to force the abbrev to upper case */
strlcpy(upabbr, abbr, sizeof(upabbr)); for (p = (unsignedchar *) upabbr; *p; p++)
*p = pg_toupper(*p);
/* Look up the abbrev's meaning in this zone */ if (pg_timezone_abbrev_is_known(upabbr,
isfixed,
&gmtoff,
isdst,
tzp))
{ /* Change sign to agree with DetermineTimeZoneOffset() */
*offset = (int) -gmtoff; returntrue;
} returnfalse;
}
/* DecodeTimeOnly() *Interpretparsedstringastimefieldsonly. *Returns0ifsuccessful,DTERRcodeifbogusinputdetected. * *Inputsarefield[]andftype[]arrays,oflengthnf. *Otherargumentsareoutputs. * *Notethatsupportfortimezoneisherefor *SQLTIMEWITHTIMEZONE,butitreveals *bogositywithSQLdate/timestandards,since *wemustinferatimezonefromcurrenttime. *-thomas2000-03-10 *Allowspecifyingdatetogetabettertimezone, *iftimezonesareallowed.-thomas2001-12-26
*/ int
DecodeTimeOnly(char **field, int *ftype, int nf, int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
DateTimeErrorExtra *extra)
{ int fmask = 0,
tmask,
type; int ptype = 0; /* "prefix type" for ISO and Julian formats */ int i; int val; int dterr; 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;
for (i = 0; i < nf; i++)
{ switch (ftype[i])
{ case DTK_DATE:
/* *Timezonenotallowed?Thenshouldnotacceptdatesortime *zonesnomatterwhatelse!
*/ if (tzp == NULL) return DTERR_BAD_FORMAT;
/* Under limited circumstances, we will accept a date... */ if (i == 0 && nf >= 2 &&
(ftype[nf - 1] == DTK_DATE || ftype[1] == DTK_TIME))
{
dterr = DecodeDate(field[i], fmask,
&tmask, &is2digits, tm); if (dterr) return dterr;
} /* otherwise, this is a time and/or time zone */ else
{ if (isdigit((unsignedchar) *field[i]))
{ char *cp;
case DTK_TIME: /* previous field was "t" for ISO time */
dterr = DecodeNumberField(strlen(field[i]), field[i],
(fmask | DTK_DATE_M),
&tmask, tm,
fsec, &is2digits); if (dterr < 0) return dterr;
ftype[i] = dterr;
if (tmask != DTK_TIME_M) return DTERR_BAD_FORMAT; break;
case DTK_STRING: case DTK_SPECIAL: /* timezone abbrevs take precedence over built-in tokens */
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) continue;
/* daylight savings time modifier disallowed with full TZ */ if (fmask & DTK_M(DTZMOD)) return DTERR_BAD_FORMAT;
/* if non-DST zone, we do not need to know the date */ if (pg_get_timezone_offset(namedTz, &gmtoff))
{
*tzp = -(int) gmtoff;
} else
{ /* a date has to be specified */ if ((fmask & DTK_DATE_M) != DTK_DATE_M) return DTERR_BAD_FORMAT;
*tzp = DetermineTimeZoneOffset(tm, namedTz);
}
}
/* *daylightsavingstimemodifierbutnostandardtimezone?thenerror
*/ 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,
*tmp = &tt;
/* *daylightsavingstimemodifierbutnostandardtimezone?thenerror
*/ 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 = DetermineTimeZoneOffset(tmp, session_timezone);
tm->tm_isdst = tmp->tm_isdst;
}
return0;
}
/* DecodeDate() *Decodedatestringwhichincludesdelimiters. *Return0ifokay,aDTERRcodeifnot. * *str:fieldtobeparsed *fmask:bitmaskforfieldtypesalreadyseen **tmask:receivesbitmaskforfieldsfoundhere **is2digits:settotrueifwefind2-digityear **tm:fieldvaluesarestoredintoappropriatemembersofthisstruct
*/ staticint
DecodeDate(char *str, int fmask, int *tmask, bool *is2digits, struct pg_tm *tm)
{
fsec_t fsec; int nf = 0; int i,
len; int dterr; bool haveTextMonth = false; int type,
val,
dmask = 0; char *field[MAXDATEFIELDS];
*tmask = 0;
/* parse this string... */ while (*str != '\0' && nf < MAXDATEFIELDS)
{ /* skip field separators */ while (*str != '\0' && !isalnum((unsignedchar) *str))
str++;
if (*str == '\0') return DTERR_BAD_FORMAT; /* end of string after separator */
field[nf] = str; if (isdigit((unsignedchar) *str))
{ while (isdigit((unsignedchar) *str))
str++;
} elseif (isalpha((unsignedchar) *str))
{ while (isalpha((unsignedchar) *str))
str++;
}
/* Just get rid of any non-digit, non-alpha characters... */ if (*str != '\0')
*str++ = '\0';
nf++;
}
/* look first for text fields, since that will be unambiguous month */ for (i = 0; i < nf; i++)
{ if (isalpha((unsignedchar) *field[i]))
{
type = DecodeSpecial(i, field[i], &val); if (type == IGNORE_DTF) continue;
if ((fmask & ~(DTK_M(DOY) | DTK_M(TZ))) != DTK_DATE_M) return DTERR_BAD_FORMAT;
/* validation of the field values must wait until ValidateDate() */
return0;
}
/* ValidateDate() *Checkvalidyear/month/dayvalues,handleBCandDOYcases *Return0ifokay,aDTERRcodeifnot.
*/ int
ValidateDate(int fmask, bool isjulian, bool is2digits, bool bc, struct pg_tm *tm)
{ if (fmask & DTK_M(YEAR))
{ if (isjulian)
{ /* tm_year is correct and should not be touched */
} elseif (bc)
{ /* there is no year zero in AD/BC notation */ if (tm->tm_year <= 0) return DTERR_FIELD_OVERFLOW; /* internally, we represent 1 BC as year zero, 2 BC as -1, etc */
tm->tm_year = -(tm->tm_year - 1);
} elseif (is2digits)
{ /* process 1 or 2-digit input as 1970-2069 AD, allow '0' and '00' */ if (tm->tm_year < 0) /* just paranoia */ return DTERR_FIELD_OVERFLOW; if (tm->tm_year < 70)
tm->tm_year += 2000; elseif (tm->tm_year < 100)
tm->tm_year += 1900;
} else
{ /* there is no year zero in AD/BC notation */ if (tm->tm_year <= 0) return DTERR_FIELD_OVERFLOW;
}
}
/* now that we have correct year, decode DOY */ if (fmask & DTK_M(DOY))
{
j2date(date2j(tm->tm_year, 1, 1) + tm->tm_yday - 1,
&tm->tm_year, &tm->tm_mon, &tm->tm_mday);
}
/* check for valid month */
if (fmask & DTK_M(MONTH))
{
if (tm->tm_mon < 1 || tm->tm_mon > MONTHS_PER_YEAR)
return DTERR_MD_FIELD_OVERFLOW;
}
/* minimal check for valid day */
if (fmask & DTK_M(DAY))
{
if (tm->tm_mday < 1 || tm->tm_mday > 31)
return DTERR_MD_FIELD_OVERFLOW;
}
if ((fmask & DTK_DATE_M) == DTK_DATE_M)
{
/*
* Check for valid day of month, now that we know for sure the month
* and year. Note we don't use MD_FIELD_OVERFLOW here, since it seems
* unlikely that "Feb 29" is a YMD-order error.
*/
if (tm->tm_mday > day_tab[isleap(tm->tm_year)][tm->tm_mon - 1])
return DTERR_FIELD_OVERFLOW;
}
return 0;
}
/* DecodeTimeCommon()
* Decode time string which includes delimiters.
* Return 0 if okay, a DTERR code if not.
* tmask and itm are output parameters.
*
* This code is shared between the timestamp and interval cases.
* We return a struct pg_itm (of which only the tm_usec, tm_sec, tm_min,
* and tm_hour fields are used) and let the wrapper functions below
* convert and range-check as necessary.
*/
static int
DecodeTimeCommon(char *str, int fmask, int range,
int *tmask, struct pg_itm *itm)
{
char *cp;
int dterr;
fsec_t fsec = 0;
*tmask = DTK_TIME_M;
errno = 0;
itm->tm_hour = strtoi64(str, &cp, 10);
if (errno == ERANGE)
return DTERR_FIELD_OVERFLOW;
if (*cp != ':')
return DTERR_BAD_FORMAT;
errno = 0;
itm->tm_min = strtoint(cp + 1, &cp, 10);
if (errno == ERANGE)
return DTERR_FIELD_OVERFLOW;
if (*cp == '\0')
{
itm->tm_sec = 0;
/* If it's a MINUTE TO SECOND interval, take 2 fields as being mm:ss */
if (range == (INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND)))
{
if (itm->tm_hour > INT_MAX || itm->tm_hour < INT_MIN)
return DTERR_FIELD_OVERFLOW;
itm->tm_sec = itm->tm_min;
itm->tm_min = (int) itm->tm_hour;
itm->tm_hour = 0;
}
}
else if (*cp == '.')
{
/* always assume mm:ss.sss is MINUTE TO SECOND */
dterr = ParseFractionalSecond(cp, &fsec);
if (dterr)
return dterr;
if (itm->tm_hour > INT_MAX || itm->tm_hour < INT_MIN)
return DTERR_FIELD_OVERFLOW;
itm->tm_sec = itm->tm_min;
itm->tm_min = (int) itm->tm_hour;
itm->tm_hour = 0;
}
else if (*cp == ':')
{
errno = 0;
itm->tm_sec = strtoint(cp + 1, &cp, 10);
if (errno == ERANGE)
return DTERR_FIELD_OVERFLOW;
if (*cp == '.')
{
dterr = ParseFractionalSecond(cp, &fsec);
if (dterr)
return dterr;
}
else if (*cp != '\0')
return DTERR_BAD_FORMAT;
}
else
return DTERR_BAD_FORMAT;
/* do a sanity check; but caller must check the range of tm_hour */
if (itm->tm_hour < 0 ||
itm->tm_min < 0 || itm->tm_min > MINS_PER_HOUR - 1 ||
itm->tm_sec < 0 || itm->tm_sec > SECS_PER_MINUTE ||
fsec < 0 || fsec > USECS_PER_SEC)
return DTERR_FIELD_OVERFLOW;
itm->tm_usec = (int) fsec;
return 0;
}
/* DecodeTime()
* Decode time string which includes delimiters.
* Return 0 if okay, a DTERR code if not.
*
* This version is used for timestamps. The results are returned into
* the tm_hour/tm_min/tm_sec fields of *tm, and microseconds into *fsec.
*/
static int
DecodeTime(char *str, int fmask, int range,
int *tmask, struct pg_tm *tm, fsec_t *fsec)
{
struct pg_itm itm;
int dterr;
/* DecodeTimeForInterval()
* Decode time string which includes delimiters.
* Return 0 if okay, a DTERR code if not.
*
* This version is used for intervals. The results are returned into
* itm_in->tm_usec.
*/
static int
DecodeTimeForInterval(char *str, int fmask, int range,
int *tmask, struct pg_itm_in *itm_in)
{
struct pg_itm itm;
int dterr;
/* DecodeNumber()
* Interpret plain numeric field as a date value in context.
* Return 0 if okay, a DTERR code if not.
*/
static int
DecodeNumber(int flen, char *str, bool haveTextMonth, int fmask,
int *tmask, struct pg_tm *tm, fsec_t *fsec, bool *is2digits)
{
int val;
char *cp;
int dterr;
*tmask = 0;
errno = 0;
val = strtoint(str, &cp, 10);
if (errno == ERANGE)
return DTERR_FIELD_OVERFLOW;
if (cp == str)
return DTERR_BAD_FORMAT;
if (*cp == '.')
{
/*
* More than two digits before decimal point? Then could be a date or
* a run-together time: 2001.36020011225040506.789
*/
if (cp - str > 2)
{
dterr = DecodeNumberField(flen, str,
(fmask | DTK_DATE_M),
tmask, tm,
fsec, is2digits);
if (dterr < 0)
return dterr;
return 0;
}
dterr = ParseFractionalSecond(cp, fsec);
if (dterr)
return dterr;
}
else if (*cp != '\0')
return DTERR_BAD_FORMAT;
/* Special case for day of year */
if (flen == 3 && (fmask & DTK_DATE_M) == DTK_M(YEAR) && val >= 1 &&
val <= 366)
{
*tmask = (DTK_M(DOY) | DTK_M(MONTH) | DTK_M(DAY));
tm->tm_yday = val;
/* tm_mon and tm_mday can't actually be set yet ... */
return 0;
}
/* Switch based on what we have so far */
switch (fmask & DTK_DATE_M)
{
case 0:
/*
* Nothing so far; make a decision about what we think the input
* 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.
*/
if (flen >= 3 || DateOrder == DATEORDER_YMD)
{
*tmask = DTK_M(YEAR);
tm->tm_year = val;
}
else if (DateOrder == DATEORDER_DMY)
{
*tmask = DTK_M(DAY);
tm->tm_mday = val;
}
else
{
*tmask = DTK_M(MONTH);
tm->tm_mon = val;
}
break;
case (DTK_M(YEAR)):
/* Must be at second field of YY-MM-DD */
*tmask = DTK_M(MONTH);
tm->tm_mon = val;
break;
case (DTK_M(MONTH)):
if (haveTextMonth)
{
/*
* We are at the first numeric field of a date that included a
* 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
* either DMY or MDY modes, as well as YY-MON-DD in YMD mode.
*/
if (flen >= 3 || DateOrder == DATEORDER_YMD)
{
*tmask = DTK_M(YEAR);
tm->tm_year = val;
}
else
{
*tmask = DTK_M(DAY);
tm->tm_mday = val;
}
}
else
{
/* Must be at second field of MM-DD-YY */
*tmask = DTK_M(DAY);
tm->tm_mday = val;
}
break;
case (DTK_M(YEAR) | DTK_M(MONTH)):
if (haveTextMonth)
{
/* Need to accept DD-MON-YYYY even in YMD mode */
if (flen >= 3 && *is2digits)
{
/* Guess that first numeric field is day was wrong */
*tmask = DTK_M(DAY); /* YEAR is already set */
tm->tm_mday = tm->tm_year;
tm->tm_year = val;
*is2digits = false;
}
else
{
*tmask = DTK_M(DAY);
tm->tm_mday = val;
}
}
else
{
/* Must be at third field of YY-MM-DD */
*tmask = DTK_M(DAY);
tm->tm_mday = val;
}
break;
case (DTK_M(DAY)):
/* Must be at second field of DD-MM-YY */
*tmask = DTK_M(MONTH);
tm->tm_mon = val;
break;
case (DTK_M(MONTH) | DTK_M(DAY)):
/* Must be at third field of DD-MM-YY or MM-DD-YY */
*tmask = DTK_M(YEAR);
tm->tm_year = val;
break;
case (DTK_M(YEAR) | DTK_M(MONTH) | DTK_M(DAY)):
/* we have all the date, so it must be a time field */
dterr = DecodeNumberField(flen, str, fmask,
tmask, tm,
fsec, is2digits);
if (dterr < 0)
return dterr;
return 0;
/*
* When processing a year field, mark it for adjustment if it's only one
* or two digits.
*/
if (*tmask == DTK_M(YEAR))
*is2digits = (flen <= 2);
return 0;
}
/* DecodeNumberField()
* Interpret numeric string as a concatenated date or time field.
* Return a DTK token (>= 0) if successful, a DTERR code (< 0) if not.
*
* Use the context of previously decoded fields to help with
* the interpretation.
*/
static int
DecodeNumberField(int len, char *str, int fmask,
int *tmask, struct pg_tm *tm, fsec_t *fsec, bool *is2digits)
{
char *cp;
/*
* This function was originally meant to cope only with DTK_NUMBER fields,
* but we now sometimes abuse it to parse (parts of) DTK_DATE fields,
* which can contain letters and other punctuation. Reject if it's not a
* valid DTK_NUMBER, that is digits and decimal point(s). (ParseFraction
* will reject if there's more than one decimal point.)
*/
if (strspn(str, "0123456789.") != len)
return DTERR_BAD_FORMAT;
/*
* Have a decimal point? Then this is a date or something with a seconds
* field...
*/
if ((cp = strchr(str, '.')) != NULL)
{
int dterr;
/* Convert the fraction and store at *fsec */
dterr = ParseFractionalSecond(cp, fsec);
if (dterr)
return dterr;
/* 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)
{
if (len >= 6)
{
*tmask = DTK_DATE_M;
/*
* 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;
}
}
/* not all time fields are specified? */
if ((fmask & DTK_TIME_M) != DTK_TIME_M)
{
/* hhmmss */
if (len == 6)
{
*tmask = DTK_TIME_M;
tm->tm_sec = atoi(str + 4);
*(str + 4) = '\0';
tm->tm_min = atoi(str + 2);
*(str + 2) = '\0';
tm->tm_hour = atoi(str);
/* DecodeTimezone()
* Interpret string as a numeric timezone.
*
* Return 0 if okay (and set *tzp), a DTERR code if not okay.
*/
int
DecodeTimezone(const char *str, int *tzp)
{
int tz;
int hr,
min,
sec = 0;
char *cp;
/* leading character must be "+" or "-" */
if (*str != '+' && *str != '-')
return DTERR_BAD_FORMAT;
/* DecodeTimezoneAbbrev()
* Interpret string as a timezone abbreviation, if possible.
*
* 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
* will be related in format.
*/
int
DecodeTimezoneAbbrev(int field, const char *lowtoken,
int *ftype, int *offset, pg_tz **tz,
DateTimeErrorExtra *extra)
{
TzAbbrevCache *tzc = &tzabbrevcache[field];
bool isfixed;
int isdst;
const datetkn *tp;
/*
* Do we have a cached result? Use strncmp so that we match truncated
* names, although we shouldn't really see that happen with normal
* abbreviations.
*/
if (strncmp(lowtoken, tzc->abbrev, TOKMAXLEN) == 0)
{
*ftype = tzc->ftype;
*offset = tzc->offset;
*tz = tzc->tz;
return 0;
}
/*
* See if the current session_timezone recognizes it. Checking this
* before zoneabbrevtbl allows us to correctly handle abbreviations whose
* meaning varies across zones, such as "LMT".
*/
if (session_timezone &&
TimeZoneAbbrevIsKnown(lowtoken, session_timezone,
&isfixed, offset, &isdst))
{
*ftype = (isfixed ? (isdst ? DTZ : TZ) : DYNTZ);
*tz = (isfixed ? NULL : session_timezone);
/* flip sign to agree with the convention used in zoneabbrevtbl */
*offset = -(*offset);
/* cache result; use strlcpy to truncate name if necessary */
strlcpy(tzc->abbrev, lowtoken, TOKMAXLEN + 1);
tzc->ftype = *ftype;
tzc->offset = *offset;
tzc->tz = *tz;
return 0;
}
/* Nope, so look in zoneabbrevtbl */
if (zoneabbrevtbl)
tp = datebsearch(lowtoken, zoneabbrevtbl->abbrevs,
zoneabbrevtbl->numabbrevs);
else
tp = NULL;
if (tp == NULL)
{
*ftype = UNKNOWN_FIELD;
*offset = 0;
*tz = NULL;
/* failure results are not cached */
}
else
{
*ftype = tp->type;
if (tp->type == DYNTZ)
{
*offset = 0;
*tz = FetchDynamicTimeZone(zoneabbrevtbl, tp, extra);
if (*tz == NULL)
return DTERR_BAD_ZONE_ABBREV;
}
else
{
*offset = tp->value;
*tz = NULL;
}
/* cache result; use strlcpy to truncate name if necessary */
strlcpy(tzc->abbrev, lowtoken, TOKMAXLEN + 1);
tzc->ftype = *ftype;
tzc->offset = *offset;
tzc->tz = *tz;
}
return 0;
}
/*
* Reset tzabbrevcache after a change in session_timezone.
*/
void
ClearTimeZoneAbbrevCache(void)
{
memset(tzabbrevcache, 0, sizeof(tzabbrevcache));
}
/* 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.
*/
int
DecodeSpecial(int field, const char *lowtoken, int *val)
{
int type;
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, datetktbl, szdatetktbl);
}
if (tp == NULL)
{ type = UNKNOWN_FIELD;
*val = 0;
}
else
{
datecache[field] = tp; type = tp->type;
*val = tp->value;
}
return type;
}
/* DecodeTimezoneName()
* Interpret string as a timezone abbreviation or name.
* Throw error if the name is not recognized.
*
* The return value indicates what kind of zone identifier it is:
* TZNAME_FIXED_OFFSET: fixed offset from UTC
* TZNAME_DYNTZ: dynamic timezone abbreviation
* TZNAME_ZONE: full tzdb zone name
*
* For TZNAME_FIXED_OFFSET, *offset receives the UTC offset (in seconds,
* with ISO sign convention: positive is east of Greenwich).
* For the other two cases, *tz receives the timezone struct representing
* the zone name or the abbreviation's underlying zone.
*/
int
DecodeTimezoneName(const char *tzname, int *offset, pg_tz **tz)
{
char *lowzone;
int dterr, type;
DateTimeErrorExtra extra;
/*
* First we look in the timezone abbreviation table (to handle cases like
* "EST"), and if that fails, we look in the timezone database (to handle
* cases like "America/New_York"). This matches the order in which
* timestamp input checks the cases; it's important because the timezone
* database unwisely uses a few zone names that are identical to offset
* abbreviations.
*/
if (type == TZ || type == DTZ)
{
/* fixed-offset abbreviation, return the offset */
return TZNAME_FIXED_OFFSET;
}
else if (type == DYNTZ)
{
/* dynamic-offset abbreviation, return its referenced timezone */
return TZNAME_DYNTZ;
}
else
{
/* try it as a full zone name */
*tz = pg_tzset(tzname);
if (*tz == NULL)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("time zone \"%s\" not recognized", tzname)));
return TZNAME_ZONE;
}
}
/* DecodeTimezoneNameToTz()
* Interpret string as a timezone abbreviation or name.
* Throw error if the name is not recognized.
*
* This is a simple wrapper for DecodeTimezoneName that produces a pg_tz *
* result in all cases.
*/
pg_tz *
DecodeTimezoneNameToTz(const char *tzname)
{
pg_tz *result;
int offset;
if (DecodeTimezoneName(tzname, &offset, &result) == TZNAME_FIXED_OFFSET)
{
/* fixed-offset abbreviation, get a pg_tz descriptor for that */
result = pg_tzset_offset(-offset); /* flip to POSIX sign convention */
}
return result;
}
/* DecodeTimezoneAbbrevPrefix()
* Interpret prefix of string as a timezone abbreviation, if possible.
*
* This has roughly the same functionality as DecodeTimezoneAbbrev(),
* but the API is adapted to the needs of formatting.c. Notably,
* we will match the longest possible prefix of the given string
* rather than insisting on a complete match, and downcasing is applied
* here rather than in the caller.
*
* Returns the length of the timezone abbreviation, or -1 if not recognized.
* On success, sets *offset to the GMT offset for the abbreviation if it
* is a fixed-offset abbreviation, or sets *tz to the pg_tz struct for
* a dynamic abbreviation.
*/
int
DecodeTimezoneAbbrevPrefix(const char *str, int *offset, pg_tz **tz)
{
char lowtoken[TOKMAXLEN + 1];
int len;
*offset = 0; /* avoid uninitialized vars on failure */
*tz = NULL;
/* Downcase as much of the string as we could need */
for (len = 0; len < TOKMAXLEN; len++)
{
if (*str == '\0' || !isalpha((unsigned char) *str))
break;
lowtoken[len] = pg_tolower((unsigned char) *str++);
}
lowtoken[len] = '\0';
/*
* We could avoid doing repeated binary searches if we cared to duplicate
* datebsearch here, but it's not clear that such an optimization would be
* worth the trouble. In common cases there's probably not anything after
* the zone abbrev anyway. So just search with successively truncated
* strings.
*/
while (len > 0)
{
bool isfixed;
int isdst;
const datetkn *tp;
/* See if the current session_timezone recognizes it. */
if (session_timezone &&
TimeZoneAbbrevIsKnown(lowtoken, session_timezone,
&isfixed, offset, &isdst))
{
if (isfixed)
{
/* flip sign to agree with the convention in zoneabbrevtbl */
*offset = -(*offset);
}
else
{
/* Caller must resolve the abbrev's current meaning */
*tz = session_timezone;
}
return len;
}
/* Known in zoneabbrevtbl? */
if (zoneabbrevtbl)
tp = datebsearch(lowtoken, zoneabbrevtbl->abbrevs,
zoneabbrevtbl->numabbrevs);
else
tp = NULL;
if (tp != NULL)
{
if (tp->type == DYNTZ)
{
DateTimeErrorExtra extra;
pg_tz *tzp = FetchDynamicTimeZone(zoneabbrevtbl, tp,
&extra);
if (tzp != NULL)
{
/* Caller must resolve the abbrev's current meaning */
*tz = tzp;
return len;
}
}
else
{
/* Fixed-offset zone abbrev, so it's easy */
*offset = tp->value;
return len;
}
}
/* Nope, try the next shorter string. */
lowtoken[--len] = '\0';
}
/* Did not find a match */
return -1;
}
/* ClearPgItmIn
*
* Zero out a pg_itm_in
*/
static inline void
ClearPgItmIn(struct pg_itm_in *itm_in)
{
itm_in->tm_usec = 0;
itm_in->tm_mday = 0;
itm_in->tm_mon = 0;
itm_in->tm_year = 0;
}
/* DecodeInterval()
* Interpret previously parsed fields for general time interval.
* Returns 0 if successful, DTERR code if bogus input detected.
* dtype and itm_in are output parameters.
*
* Allow "date" field DTK_DATE since this could be just
* an unsigned floating point number. - thomas 1997-11-16
*
* Allow ISO-style time span, with implicit units on number of days
* preceding an hh:mm:ss field. - thomas 1998-04-30
*
* itm_in remains undefined for infinite interval values for which dtype alone
* suffices.
*/
int
DecodeInterval(char **field, int *ftype, int nf, int range,
int *dtype, struct pg_itm_in *itm_in)
{
bool force_negative = false;
bool is_before = false;
bool parsing_unit_val = false;
char *cp;
int fmask = 0,
tmask, type,
uval;
int i;
int dterr;
int64 val;
double fval;
*dtype = DTK_DELTA; type = IGNORE_DTF;
ClearPgItmIn(itm_in);
/*----------
* The SQL standard defines the interval literal
* '-1 1:00:00'
* to mean "negative 1 days and negative 1 hours", while Postgres
* traditionally treats this as meaning "negative 1 days and positive
* 1 hours". In SQL_STANDARD intervalstyle, we apply the leading sign
* to all fields if there are no other explicit signs.
*
* We leave the signs alone if there are additional explicit signs.
* This protects us against misinterpreting postgres-style dump output,
* since the postgres-style output code has always put an explicit sign on
* all fields following a negative field. But note that SQL-spec output
* is ambiguous and can be misinterpreted on load! (So it's best practice
* to dump in postgres style, not SQL style.)
*----------
*/
if (IntervalStyle == INTSTYLE_SQL_STANDARD && nf > 0 && *field[0] == '-')
{
force_negative = true;
/* Check for additional explicit signs */
for (i = 1; i < nf; i++)
{
if (*field[i] == '-' || *field[i] == '+')
{
force_negative = false;
break;
}
}
}
/* read through list backwards to pick up units before values */
for (i = nf - 1; i >= 0; i--)
{
switch (ftype[i])
{
case DTK_TIME:
dterr = DecodeTimeForInterval(field[i], fmask, range,
&tmask, itm_in);
if (dterr)
return dterr;
if (force_negative &&
itm_in->tm_usec > 0)
itm_in->tm_usec = -itm_in->tm_usec; type = DTK_DAY;
parsing_unit_val = false;
break;
case DTK_TZ:
/*
* 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] == '+');
/*
* 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 &&
DecodeTimeForInterval(field[i] + 1, fmask, range,
&tmask, itm_in) == 0)
{
if (*field[i] == '-')
{
/* flip the sign on time field */
if (itm_in->tm_usec == PG_INT64_MIN)
return DTERR_FIELD_OVERFLOW;
itm_in->tm_usec = -itm_in->tm_usec;
}
if (force_negative &&
itm_in->tm_usec > 0)
itm_in->tm_usec = -itm_in->tm_usec;
/*
* Set the next type to be a day, if units are not
* specified. This handles the case of '1 +02:03' since we
* are reading right to left.
*/ type = DTK_DAY;
parsing_unit_val = false;
break;
}
/*
* 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:
if (type == IGNORE_DTF)
{
/* use typmod to decide what rightmost field is */
switch (range)
{
case INTERVAL_MASK(YEAR): type = DTK_YEAR;
break;
case INTERVAL_MASK(MONTH):
case INTERVAL_MASK(YEAR) | INTERVAL_MASK(MONTH): type = DTK_MONTH;
break;
case INTERVAL_MASK(DAY): type = DTK_DAY;
break;
case INTERVAL_MASK(HOUR):
case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR): type = DTK_HOUR;
break;
case INTERVAL_MASK(MINUTE):
case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE):
case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE): type = DTK_MINUTE;
break;
case INTERVAL_MASK(SECOND):
case INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND): type = DTK_SECOND;
break;
default: type = DTK_SECOND;
break;
}
}
errno = 0;
val = strtoi64(field[i], &cp, 10);
if (errno == ERANGE)
return DTERR_FIELD_OVERFLOW;
if (*cp == '-')
{
/* SQL "years-months" syntax */
int val2;
val2 = strtoint(cp + 1, &cp, 10);
if (errno == ERANGE || val2 < 0 || val2 >= MONTHS_PER_YEAR)
return DTERR_FIELD_OVERFLOW;
if (*cp != '\0')
return DTERR_BAD_FORMAT; type = DTK_MONTH;
if (*field[i] == '-')
val2 = -val2;
if (pg_mul_s64_overflow(val, MONTHS_PER_YEAR, &val))
return DTERR_FIELD_OVERFLOW;
if (pg_add_s64_overflow(val, val2, &val))
return DTERR_FIELD_OVERFLOW;
fval = 0;
}
else if (*cp == '.')
{
dterr = ParseFraction(cp, &fval);
if (dterr)
return dterr;
if (*field[i] == '-')
fval = -fval;
}
else if (*cp == '\0')
fval = 0;
else
return DTERR_BAD_FORMAT;
tmask = 0; /* DTK_M(type); */
if (force_negative)
{
/* val and fval should be of same sign, but test anyway */
if (val > 0)
val = -val;
if (fval > 0)
fval = -fval;
}
switch (type)
{
case DTK_MICROSEC:
if (!AdjustMicroseconds(val, fval, 1, itm_in))
return DTERR_FIELD_OVERFLOW;
tmask = DTK_M(MICROSECOND);
break;
case DTK_MILLISEC:
if (!AdjustMicroseconds(val, fval, 1000, itm_in))
return DTERR_FIELD_OVERFLOW;
tmask = DTK_M(MILLISECOND);
break;
case DTK_SECOND:
if (!AdjustMicroseconds(val, fval, USECS_PER_SEC, itm_in))
return DTERR_FIELD_OVERFLOW;
/*
* If any subseconds were specified, consider this
* microsecond and millisecond input as well.
*/
if (fval == 0)
tmask = DTK_M(SECOND);
else
tmask = DTK_ALL_SECS_M;
break;
case DTK_MINUTE:
if (!AdjustMicroseconds(val, fval, USECS_PER_MINUTE, itm_in))
return DTERR_FIELD_OVERFLOW;
tmask = DTK_M(MINUTE);
break;
case DTK_HOUR:
if (!AdjustMicroseconds(val, fval, USECS_PER_HOUR, itm_in))
return DTERR_FIELD_OVERFLOW;
tmask = DTK_M(HOUR); type = DTK_DAY; /* set for next field */
break;
case DTK_DAY:
if (!AdjustDays(val, 1, itm_in) ||
!AdjustFractMicroseconds(fval, USECS_PER_DAY, itm_in))
return DTERR_FIELD_OVERFLOW;
tmask = DTK_M(DAY);
break;
case DTK_WEEK:
if (!AdjustDays(val, 7, itm_in) ||
!AdjustFractDays(fval, 7, itm_in))
return DTERR_FIELD_OVERFLOW;
tmask = DTK_M(WEEK);
break;
case DTK_MONTH:
if (!AdjustMonths(val, itm_in) ||
!AdjustFractDays(fval, DAYS_PER_MONTH, itm_in))
return DTERR_FIELD_OVERFLOW;
tmask = DTK_M(MONTH);
break;
case DTK_YEAR:
if (!AdjustYears(val, 1, itm_in) ||
!AdjustFractYears(fval, 1, itm_in))
return DTERR_FIELD_OVERFLOW;
tmask = DTK_M(YEAR);
break;
case DTK_DECADE:
if (!AdjustYears(val, 10, itm_in) ||
!AdjustFractYears(fval, 10, itm_in))
return DTERR_FIELD_OVERFLOW;
tmask = DTK_M(DECADE);
break;
case DTK_CENTURY:
if (!AdjustYears(val, 100, itm_in) ||
!AdjustFractYears(fval, 100, itm_in))
return DTERR_FIELD_OVERFLOW;
tmask = DTK_M(CENTURY);
break;
case DTK_MILLENNIUM:
if (!AdjustYears(val, 1000, itm_in) ||
!AdjustFractYears(fval, 1000, itm_in))
return DTERR_FIELD_OVERFLOW;
tmask = DTK_M(MILLENNIUM);
break;
case DTK_STRING:
case DTK_SPECIAL:
/* reject consecutive unhandled units */
if (parsing_unit_val)
return DTERR_BAD_FORMAT; type = DecodeUnits(i, field[i], &uval);
if (type == UNKNOWN_FIELD) type = DecodeSpecial(i, field[i], &uval);
if (type == IGNORE_DTF)
continue;
tmask = 0; /* DTK_M(type); */
switch (type)
{
case UNITS: type = uval;
parsing_unit_val = true;
break;
case AGO:
/*
* "ago" is only allowed to appear at the end of the
* interval.
*/
if (i != nf - 1)
return DTERR_BAD_FORMAT;
is_before = true; type = uval;
break;
case RESERV:
tmask = (DTK_DATE_M | DTK_TIME_M);
/*
* Only reserved words corresponding to infinite
* intervals are accepted.
*/
if (uval != DTK_LATE && uval != DTK_EARLY)
return DTERR_BAD_FORMAT;
/*
* Infinity cannot be followed by anything else. We
* could allow "ago" to reverse the sign of infinity
* but using signed infinity is more intuitive.
*/
if (i != nf - 1)
return DTERR_BAD_FORMAT;
*dtype = uval;
break;
default:
return DTERR_BAD_FORMAT;
}
break;
default:
return DTERR_BAD_FORMAT;
}
if (tmask & fmask)
return DTERR_BAD_FORMAT;
fmask |= tmask;
}
/* ensure that at least one time field has been found */
if (fmask == 0)
return DTERR_BAD_FORMAT;
/* reject if unit appeared and was never handled */
if (parsing_unit_val)
return DTERR_BAD_FORMAT;
/* finally, AGO negates everything */
if (is_before)
{
if (itm_in->tm_usec == PG_INT64_MIN ||
itm_in->tm_mday == INT_MIN ||
itm_in->tm_mon == INT_MIN ||
itm_in->tm_year == INT_MIN)
return DTERR_FIELD_OVERFLOW;
/*
* Helper functions to avoid duplicated code in DecodeISO8601Interval.
*
* Parse a decimal value and break it into integer and fractional parts.
* Set *endptr to end+1 of the parsed substring.
* Returns 0 or DTERR code.
*/
static int
ParseISO8601Number(char *str, char **endptr, int64 *ipart, double *fpart)
{
double val;
/*
* Historically this has accepted anything that strtod() would take,
* notably including "e"notation, so continue doing that. This is
* slightly annoying because the precision of double is less than that of
* int64, so we would lose accuracy for inputs larger than 2^53 or so.
* However, historically we rejected inputs outside the int32 range,
* making that concern moot. What we do now is reject abs(val) above
* 1.0e15 (a round number a bit less than 2^50), so that any accepted
* value will have an exact integer part, and thereby a fraction part with
* abs(*fpart) less than 1. In the absence of field complaints it doesn't
* seem worth working harder.
*/
if (!(isdigit((unsigned char) *str) || *str == '-' || *str == '.'))
return DTERR_BAD_FORMAT;
errno = 0;
val = strtod(str, endptr);
/* did we not see anything that looks like a double? */
if (*endptr == str || errno != 0)
return DTERR_BAD_FORMAT;
/* watch out for overflow, including infinities; reject NaN too */
if (isnan(val) || val < -1.0e15 || val > 1.0e15)
return DTERR_FIELD_OVERFLOW;
/* be very sure we truncate towards zero (cf dtrunc()) */
if (val >= 0)
*ipart = (int64) floor(val);
else
*ipart = (int64) -floor(-val);
*fpart = val - *ipart;
/* Callers expect this to hold */
Assert(*fpart > -1.0 && *fpart < 1.0);
return 0;
}
/*
* Determine number of integral digits in a valid ISO8601 number field
* (we should ignore sign and any fraction part)
*/
static int
ISO8601IntegerWidth(char *fieldstart)
{
/* We might have had a leading '-' */
if (*fieldstart == '-')
fieldstart++;
return strspn(fieldstart, "0123456789");
}
/* DecodeISO8601Interval()
* Decode an ISO8601 time interval of the "format with designators"
* (section 4.4.3.2) or "alternative format" (section 4.4.3.3)
* Examples: P1D for 1 day
* PT1H for 1 hour
* P2Y6M7DT1H30M for 2 years, 6 months, 7 days 1 hour 30 min
* P0002-06-07T01:30:00 the same value in alternative format
*
* Returns 0 if successful, DTERR code if bogus input detected.
* Note: error code should be DTERR_BAD_FORMAT if input doesn't look like
* ISO8601, otherwise this could cause unexpected error messages.
* dtype and itm_in are output parameters.
*
* A couple exceptions from the spec:
* - a week field ('W') may coexist with other units
* - allows decimals in fields other than the least significant unit.
*/
int
DecodeISO8601Interval(char *str,
int *dtype, struct pg_itm_in *itm_in)
{
bool datepart = true;
bool havefield = false;
*dtype = DTK_DELTA;
ClearPgItmIn(itm_in);
if (strlen(str) < 2 || str[0] != 'P')
return DTERR_BAD_FORMAT;
str++;
while (*str)
{
char *fieldstart;
int64 val;
double fval;
char unit;
int dterr;
if (*str == 'T') /* T indicates the beginning of the time part */
{
datepart = false;
havefield = false;
str++;
continue;
}
/*
* Note: we could step off the end of the string here. Code below
* *must* exit the loop if unit == '\0'.
*/
unit = *str++;
if (datepart)
{
switch (unit) /* before T: Y M W D */
{
case 'Y':
if (!AdjustYears(val, 1, itm_in) ||
!AdjustFractYears(fval, 1, itm_in))
return DTERR_FIELD_OVERFLOW;
break;
case 'M':
if (!AdjustMonths(val, itm_in) ||
!AdjustFractDays(fval, DAYS_PER_MONTH, itm_in))
return DTERR_FIELD_OVERFLOW;
break;
case 'W':
if (!AdjustDays(val, 7, itm_in) ||
!AdjustFractDays(fval, 7, itm_in))
return DTERR_FIELD_OVERFLOW;
break;
case 'D':
if (!AdjustDays(val, 1, itm_in) ||
!AdjustFractMicroseconds(fval, USECS_PER_DAY, itm_in))
return DTERR_FIELD_OVERFLOW;
break;
case 'T': /* ISO86014.4.3.3 Alternative Format / Basic */
case '\0':
if (ISO8601IntegerWidth(fieldstart) == 8 && !havefield)
{
if (!AdjustYears(val / 10000, 1, itm_in) ||
!AdjustMonths((val / 100) % 100, itm_in) ||
!AdjustDays(val % 100, 1, itm_in) ||
!AdjustFractMicroseconds(fval, USECS_PER_DAY, itm_in))
return DTERR_FIELD_OVERFLOW;
if (unit == '\0')
return 0;
datepart = false;
havefield = false;
continue;
}
/* Else fall through to extended alternative format */
/* FALLTHROUGH */
case '-': /* ISO86014.4.3.3 Alternative Format,
* Extended */
if (havefield)
return DTERR_BAD_FORMAT;
if (!AdjustYears(val, 1, itm_in) ||
!AdjustFractYears(fval, 1, itm_in))
return DTERR_FIELD_OVERFLOW;
if (unit == '\0')
return 0;
if (unit == 'T')
{
datepart = false;
havefield = false;
continue;
}
dterr = ParseISO8601Number(str, &str, &val, &fval);
if (dterr)
return dterr;
if (!AdjustMonths(val, itm_in) ||
!AdjustFractDays(fval, DAYS_PER_MONTH, itm_in))
return DTERR_FIELD_OVERFLOW;
if (*str == '\0')
return 0;
if (*str == 'T')
{
datepart = false;
havefield = false;
continue;
}
if (*str != '-')
return DTERR_BAD_FORMAT;
str++;
dterr = ParseISO8601Number(str, &str, &val, &fval);
if (dterr)
return dterr;
if (!AdjustDays(val, 1, itm_in) ||
!AdjustFractMicroseconds(fval, USECS_PER_DAY, itm_in))
return DTERR_FIELD_OVERFLOW;
if (*str == '\0')
return 0;
if (*str == 'T')
{
datepart = false;
havefield = false;
continue;
}
return DTERR_BAD_FORMAT;
default:
/* not a valid date unit suffix */
return DTERR_BAD_FORMAT;
}
}
else
{
switch (unit) /* after T: H M S */
{
case 'H':
if (!AdjustMicroseconds(val, fval, USECS_PER_HOUR, itm_in))
return DTERR_FIELD_OVERFLOW;
break;
case 'M':
if (!AdjustMicroseconds(val, fval, USECS_PER_MINUTE, itm_in))
return DTERR_FIELD_OVERFLOW;
break;
case 'S':
if (!AdjustMicroseconds(val, fval, USECS_PER_SEC, itm_in))
return DTERR_FIELD_OVERFLOW;
break;
case '\0': /* ISO86014.4.3.3 Alternative Format */
if (ISO8601IntegerWidth(fieldstart) == 6 && !havefield)
{
if (!AdjustMicroseconds(val / 10000, 0, USECS_PER_HOUR, itm_in) ||
!AdjustMicroseconds((val / 100) % 100, 0, USECS_PER_MINUTE, itm_in) ||
!AdjustMicroseconds(val % 100, 0, USECS_PER_SEC, itm_in) ||
!AdjustFractMicroseconds(fval, 1, itm_in))
return DTERR_FIELD_OVERFLOW;
return 0;
}
/* Else fall through to extended alternative format */
/* FALLTHROUGH */
case ':': /* ISO86014.4.3.3 Alternative Format,
* Extended */
if (havefield)
return DTERR_BAD_FORMAT;
if (!AdjustMicroseconds(val, fval, USECS_PER_HOUR, itm_in))
return DTERR_FIELD_OVERFLOW;
if (unit == '\0')
return 0;
dterr = ParseISO8601Number(str, &str, &val, &fval);
if (dterr)
return dterr;
if (!AdjustMicroseconds(val, fval, USECS_PER_MINUTE, itm_in))
return DTERR_FIELD_OVERFLOW;
if (*str == '\0')
return 0;
if (*str != ':')
return DTERR_BAD_FORMAT;
str++;
dterr = ParseISO8601Number(str, &str, &val, &fval);
if (dterr)
return dterr;
if (!AdjustMicroseconds(val, fval, USECS_PER_SEC, itm_in))
return DTERR_FIELD_OVERFLOW;
if (*str == '\0')
return 0;
return DTERR_BAD_FORMAT;
default:
/* not a valid time unit suffix */
return DTERR_BAD_FORMAT;
}
}
havefield = true;
}
return 0;
}
/* DecodeUnits()
* Decode text string using lookup table.
*
* 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, const char *lowtoken, int *val)
{
int type;
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);
}
if (tp == NULL)
{ type = UNKNOWN_FIELD;
*val = 0;
}
else
{
deltacache[field] = tp; type = tp->type;
*val = tp->value;
}
return type;
} /* DecodeUnits() */
/*
* Report an error detected by one of the datetime input processing routines.
*
* 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.)
*
* If escontext points to an ErrorSaveContext node, that is filled instead
* of throwing an error.
*
* 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, DateTimeErrorExtra *extra,
const char *str, const char *datatype,
Node *escontext)
{
switch (dterr)
{
case DTERR_FIELD_OVERFLOW:
errsave(escontext,
(errcode(ERRCODE_DATETIME_FIELD_OVERFLOW),
errmsg("date/time field value out of range: \"%s\"",
str)));
break;
case DTERR_MD_FIELD_OVERFLOW:
/* <nanny>same as above, but add hint about DateStyle</nanny> */
errsave(escontext,
(errcode(ERRCODE_DATETIME_FIELD_OVERFLOW),
errmsg("date/time field value out of range: \"%s\"",
str),
errhint("Perhaps you need a different \"DateStyle\" setting.")));
break;
case DTERR_INTERVAL_OVERFLOW:
errsave(escontext,
(errcode(ERRCODE_INTERVAL_FIELD_OVERFLOW),
errmsg("interval field value out of range: \"%s\"",
str)));
break;
case DTERR_TZDISP_OVERFLOW:
errsave(escontext,
(errcode(ERRCODE_INVALID_TIME_ZONE_DISPLACEMENT_VALUE),
errmsg("time zone displacement out of range: \"%s\"",
str)));
break;
case DTERR_BAD_TIMEZONE:
errsave(escontext,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("time zone \"%s\" not recognized",
extra->dtee_timezone)));
break;
case DTERR_BAD_ZONE_ABBREV:
errsave(escontext,
(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:
errsave(escontext,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid input syntax for type %s: \"%s\"",
datatype, str)));
break;
}
}
/* datebsearch()
* Binary search -- from Knuth (6.2.1) Algorithm B. Special case like this
* is WAY faster than the generic bsearch().
*/
static const datetkn *
datebsearch(const char *key, const datetkn *base, int nel)
{
if (nel > 0)
{
const datetkn *last = base + nel - 1,
*position;
int result;
while (last >= base)
{
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)
{
/* 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;
}
}
return NULL;
}
/* EncodeTimezone()
* 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 char *
EncodeTimezone(char *str, int tz, int style)
{
int hour,
min,
sec;
sec = abs(tz);
min = sec / SECS_PER_MINUTE;
sec -= min * SECS_PER_MINUTE;
hour = min / MINS_PER_HOUR;
min -= hour * MINS_PER_HOUR;
/* TZ is negated compared to sign we wish to display ... */
*str++ = (tz <= 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, bool print_tz, int tz, int style, char *str)
{
str = pg_ultostr_zeropad(str, tm->tm_hour, 2);
*str++ = ':';
str = pg_ultostr_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.
*
* 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
*/
void
EncodeDateTime(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, const char *tzn, int style, char *str)
{
int day;
/*
* Note: the uses of %.*s in this function would be risky if the
* timezone names ever contain non-ASCII characters, since we are
* not being careful to do encoding-aware clipping. However, all
* TZ abbreviations in the IANA database are plain ASCII.
*/
if (print_tz)
{
if (tzn)
{
sprintf(str, " %.*s", MAXTZLEN, tzn);
str += strlen(str);
}
else
str = EncodeTimezone(str, tz, style);
}
break;
if (print_tz)
{
if (tzn)
{
sprintf(str, " %.*s", MAXTZLEN, tzn);
str += strlen(str);
}
else
{
/*
* We have a time zone, but no string version. Use the
* numeric form, but be sure to include a leading space to
* avoid formatting something which would be rejected by
* the date/time parser later. - thomas 2001-10-19
*/
*str++ = ' ';
str = EncodeTimezone(str, tz, style);
}
}
break;
}
/*
* Helper functions to avoid duplicated code in EncodeInterval.
*/
/* Append an ISO-8601-style interval field, but only if value isn't zero */
static char *
AddISO8601IntPart(char *cp, int64 value, char units)
{
if (value == 0)
return cp;
sprintf(cp, "%" PRId64 "%c", value, units);
return cp + strlen(cp);
}
/* Append a postgres-style interval field, but only if value isn't zero */
static char *
AddPostgresIntPart(char *cp, int64 value, const char *units,
bool *is_zero, bool *is_before)
{
if (value == 0)
return cp;
sprintf(cp, "%s%s%" PRId64 " %s%s",
(!*is_zero) ? " " : "",
(*is_before && value > 0) ? "+" : "",
value,
units,
(value != 1) ? "s" : "");
/*
* Each nonzero field sets is_before for (only) the next one. This is a
* tad bizarre but it's how it worked before...
*/
*is_before = (value < 0);
*is_zero = false;
return cp + strlen(cp);
}
/* Append a verbose-style interval field, but only if value isn't zero */
static char *
AddVerboseIntPart(char *cp, int64 value, const char *units,
bool *is_zero, bool *is_before)
{
if (value == 0)
return cp;
/* first nonzero value sets is_before */
if (*is_zero)
{
*is_before = (value < 0);
value = i64abs(value);
}
else if (*is_before)
value = -value;
sprintf(cp, " %" PRId64 " %s%s", value, units, (value == 1) ? "" : "s");
*is_zero = false;
return cp + strlen(cp);
}
/* EncodeInterval()
* Interpret time structure as a delta time and convert to string.
*
* Support "traditional Postgres" and ISO-8601 styles.
* Actually, afaik ISO does not address time interval formatting,
* but this looks similar to the spec for absolute date/time.
* - thomas 1998-04-30
*
* Actually, afaik, ISO8601 does specify formats for "time
* intervals...[of the]...format with time-unit designators", which
* are pretty ugly. The format looks something like
* P1Y1M1DT1H1M1.12345S
* but useful for exchanging data with computers instead of humans.
* - ron 2003-07-14
*
* And ISO's SQL 2008 standard specifies standards for
* "year-month literal"s (that look like '2-3') and
* "day-time literal"s (that look like ('4 5:6:7')
*/ void
EncodeInterval(struct pg_itm *itm, int style, char *str)
{ char *cp = str; int year = itm->tm_year; int mon = itm->tm_mon;
int64 mday = itm->tm_mday; /* tm_mday could be INT_MIN */
int64 hour = itm->tm_hour; int min = itm->tm_min; int sec = itm->tm_sec; int fsec = itm->tm_usec;
bool is_before = false;
bool is_zero = true;
/* Compatible with postgresql < 8.4 when DateStyle != 'iso' */ case INTSTYLE_POSTGRES_VERBOSE: default:
strcpy(cp, "@");
cp++;
cp = AddVerboseIntPart(cp, year, "year", &is_zero, &is_before);
cp = AddVerboseIntPart(cp, mon, "mon", &is_zero, &is_before);
cp = AddVerboseIntPart(cp, mday, "day", &is_zero, &is_before);
cp = AddVerboseIntPart(cp, hour, "hour", &is_zero, &is_before);
cp = AddVerboseIntPart(cp, min, "min", &is_zero, &is_before); if (sec != 0 || fsec != 0)
{
*cp++ = ' '; if (sec < 0 || (sec == 0 && fsec < 0))
{ if (is_zero)
is_before = true; elseif (!is_before)
*cp++ = '-';
} elseif (is_before)
*cp++ = '-';
cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false); /* We output "ago", not negatives, so use abs(). */
sprintf(cp, " sec%s",
(abs(sec) != 1 || fsec != 0) ? "s" : "");
is_zero = false;
} /* identically zero? then put in a unitless zero... */ if (is_zero)
strcat(cp, " 0"); if (is_before)
strcat(cp, " ago"); break;
}
}
/* *We'vebeenburntbystupiderrorsintheorderingofthedatetkntables *oncetoooften.Arrangetocheckthemduringpostmasterstart.
*/ static bool
CheckDateTokenTable(constchar *tablename, const datetkn *base, int nel)
{
bool ok = true; int i;
for (i = 0; i < nel; i++)
{ /* 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, "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,
base[i - 1].token,
base[i].token);
ok = false;
}
} return ok;
}
/* 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++)
{
struct tzEntry *abbr = abbrevs + i;
/* Alloc the result ... */
tbl = guc_malloc(LOG, tbl_size); if (!tbl) returnNULL;
/* ... 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;
/* Look up the underlying zone if we haven't already */ if (dtza->tz == NULL)
{
dtza->tz = pg_tzset(dtza->zone); if (dtza->tz == NULL)
{ /* Ooops, bogus zone name in config file entry */
extra->dtee_timezone = dtza->zone;
extra->dtee_abbrev = tp->token;
}
} return dtza->tz;
}
/* *Thisset-returningfunctionreadsallthetimezoneabbreviations *definedbytheIANAdataforthecurrenttimezonesetting, *andreturnsasetof(abbrev,utc_offset,is_dst).
*/
Datum
pg_timezone_abbrevs_zone(PG_FUNCTION_ARGS)
{
FuncCallContext *funcctx; int *pindex;
Datum result;
HeapTuple tuple;
Datum values[3];
bool nulls[3] = {0};
TimestampTz now = GetCurrentTransactionStartTimestamp();
pg_time_t t = timestamptz_to_time_t(now); constchar *abbrev; longint gmtoff; int isdst;
struct pg_itm_in itm_in;
Interval *resInterval;
/* stuff done only on the first call of the function */ if (SRF_IS_FIRSTCALL())
{
TupleDesc tupdesc;
MemoryContext oldcontext;
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
/* allocate memory for user context */
pindex = (int *) palloc(sizeof(int));
*pindex = 0;
funcctx->user_fctx = pindex;
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
elog(ERROR, "return type must be a row type");
funcctx->tuple_desc = tupdesc;
MemoryContextSwitchTo(oldcontext);
}
/* stuff done on every call of the function */
funcctx = SRF_PERCALL_SETUP();
pindex = (int *) funcctx->user_fctx;
while ((abbrev = pg_get_next_timezone_abbrev(pindex,
session_timezone)) != NULL)
{ /* Ignore abbreviations that aren't all-alphabetic */ if (strspn(abbrev, "ABCDEFGHIJKLMNOPQRSTUVWXYZ") != strlen(abbrev)) continue;
/* Determine the current meaning of the abbrev */ if (!pg_interpret_timezone_abbrev(abbrev,
&t,
&gmtoff,
&isdst,
session_timezone)) continue; /* hm, not actually used in this zone? */
/* allocate memory for user context */
pindex = (int *) palloc(sizeof(int));
*pindex = 0;
funcctx->user_fctx = pindex;
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
elog(ERROR, "return type must be a row type");
funcctx->tuple_desc = tupdesc;
MemoryContextSwitchTo(oldcontext);
}
/* stuff done on every call of the function */
funcctx = SRF_PERCALL_SETUP();
pindex = (int *) funcctx->user_fctx;
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;
DateTimeErrorExtra extra;
TimestampTz now; int isdst;
/* search for another zone to display */ for (;;)
{
tz = pg_tzenumerate_next(tzenum); if (!tz) break;
/* Convert now() to local time in this zone */ if (timestamp2tm(GetCurrentTransactionStartTimestamp(),
&tzoff, &tm, &fsec, &tzn, tz) != 0) continue; /* ignore if conversion fails */
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.