/* ---------- *Maximallengthofonenode *----------
*/ #define DCH_MAX_ITEM_SIZ 12/* max localized day name */ #define NUM_MAX_ITEM_SIZ 8/* roman number (RN has 15 chars) */
/* ---------- *FromCharDateMode *---------- * *Thisvalueisusedtonominateoneofseveraldistinct(andmutually *exclusive)dateconventionsthatakeywordcanbelongto.
*/ typedefenum
{
FROM_CHAR_DATE_NONE = 0, /* Value does not affect date mode. */
FROM_CHAR_DATE_GREGORIAN, /* Gregorian (day, month, year) style date */
FROM_CHAR_DATE_ISOWEEK, /* ISO 8601 week date */
} FromCharDateMode;
typedefstruct
{ constchar *name; int len; int id; bool is_digit;
FromCharDateMode date_mode;
} KeyWord;
typedefstruct
{
uint8 type; /* NODE_TYPE_XXX, see below */ char character[MAX_MULTIBYTE_CHAR_LEN + 1]; /* if type is CHAR */
uint8 suffix; /* keyword prefix/suffix code, if any */ const KeyWord *key; /* if type is ACTION */
} FormatNode;
/* ---------- *Numberdescriptionstruct *----------
*/ typedefstruct
{ int pre, /* (count) numbers before decimal */
post, /* (count) numbers after decimal */
lsign, /* want locales sign */
flag, /* number parameters */
pre_lsign_num, /* tmp value for lsign */
multi, /* multiplier for 'V' */
zero_start, /* position of first zero */
zero_end, /* position of last zero */
need_locale; /* needs it locale */
} NUMDesc;
/* global cache for date/time format pictures */ static DCHCacheEntry *DCHCache[DCH_CACHE_ENTRIES]; staticint n_DCHCache = 0; /* current number of entries */ staticint DCHCounter = 0; /* aging-event counter */
/* global cache for number format pictures */ static NUMCacheEntry *NUMCache[NUM_CACHE_ENTRIES]; staticint n_NUMCache = 0; /* current number of entries */ staticint NUMCounter = 0; /* aging-event counter */
/* ---------- *Forchar->date/timeconversion *----------
*/ typedefstruct
{
FromCharDateMode mode; int hh,
pm,
mi,
ss,
ssss,
d, /* stored as 1-7, Sunday = 1, 0 means missing */
dd,
ddd,
mm,
ms,
year,
bc,
ww,
w,
cc,
j,
us,
yysz, /* is it YY or YYYY ? */
clock, /* 12 or 24 hour clock? */
tzsign, /* +1, -1, or 0 if no TZH/TZM fields */
tzh,
tzm,
ff; /* fractional precision */ bool has_tz; /* was there a TZ field? */ int gmtoffset; /* GMT offset of fixed-offset zone abbrev */
pg_tz *tzp; /* pg_tz for dynamic abbrev */ char *abbrev; /* dynamic abbrev */
} TmFromChar;
struct fmt_tz /* do_to_timestamp's timezone info output */
{ bool has_tz; /* was there any TZ/TZH/TZM field? */ int gmtoffset; /* GMT offset in seconds */
};
/* ---------- *Datetimetocharconversion * *Tosupportintervalsaswellastimestamps,weuseacustom"tm"struct *thatisalmostlikestructpg_tm,buthasa64-bittm_hourfield. *Weomitthetm_isdstandtm_zonefields,whicharenotusedhere. *----------
*/ struct fmt_tm
{ int tm_sec; int tm_min;
int64 tm_hour; int tm_mday; int tm_mon; int tm_year; int tm_wday; int tm_yday; longint tm_gmtoff;
};
/* Note: this is used to copy pg_tm to fmt_tm, so not quite a bitwise copy */ #define COPY_tm(_DST, _SRC) \ do { \
(_DST)->tm_sec = (_SRC)->tm_sec; \
(_DST)->tm_min = (_SRC)->tm_min; \
(_DST)->tm_hour = (_SRC)->tm_hour; \
(_DST)->tm_mday = (_SRC)->tm_mday; \
(_DST)->tm_mon = (_SRC)->tm_mon; \
(_DST)->tm_year = (_SRC)->tm_year; \
(_DST)->tm_wday = (_SRC)->tm_wday; \
(_DST)->tm_yday = (_SRC)->tm_yday; \
(_DST)->tm_gmtoff = (_SRC)->tm_gmtoff; \
} while(0)
/* Caution: this is used to zero both pg_tm and fmt_tm structs */ #define ZERO_tm(_X) \ do { \
memset(_X, 0, sizeof(*(_X))); \
(_X)->tm_mday = (_X)->tm_mon = 1; \
} while(0)
/* *to_char(time)appearstoto_char()asaninterval,sothischeck *isreallyforintervalandtimedatatypes.
*/ #define INVALID_FOR_INTERVAL \ do { \ if (is_interval) \
ereport(ERROR, \
(errcode(ERRCODE_INVALID_DATETIME_FORMAT), \
errmsg("invalid format specification for an interval value"), \
errhint("Intervals are not tied to specific calendar dates."))); \
} while(0)
int sign, /* '-' or '+' */
sign_wrote, /* was sign write */
num_count, /* number of write digits */
num_in, /* is inside number */
num_curr, /* current position in number */
out_pre_spaces, /* spaces before first digit */
read_dec, /* to_number - was read dec. point */
read_post, /* to_number - number of dec. digit */
read_pre; /* to_number - number non-dec. digit */
char *number, /* string with number */
*number_p, /* pointer to current number position */
*inout, /* in / out buffer */
*inout_p; /* pointer to current inout position */
constchar *last_relevant, /* last relevant number after decimal point */
if (IS_EEEE(num) && n->key->id != NUM_E)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("\"EEEE\" must be the last pattern used")));
switch (n->key->id)
{ case NUM_9: if (IS_BRACKET(num))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("\"9\" must be ahead of \"PR\""))); if (IS_MULTI(num))
{
++num->multi; break;
} if (IS_DECIMAL(num))
++num->post; else
++num->pre; break;
case NUM_0: if (IS_BRACKET(num))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("\"0\" must be ahead of \"PR\""))); if (!IS_ZERO(num) && !IS_DECIMAL(num))
{
num->flag |= NUM_F_ZERO;
num->zero_start = num->pre + 1;
} if (!IS_DECIMAL(num))
++num->pre; else
++num->post;
num->zero_end = num->pre + num->post; break;
case NUM_B: if (num->pre == 0 && num->post == 0 && (!IS_ZERO(num)))
num->flag |= NUM_F_BLANK; break;
case NUM_D:
num->flag |= NUM_F_LDECIMAL;
num->need_locale = true; /* FALLTHROUGH */ case NUM_DEC: if (IS_DECIMAL(num))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple decimal points"))); if (IS_MULTI(num))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot use \"V\" and decimal point together")));
num->flag |= NUM_F_DECIMAL; break;
case NUM_FM:
num->flag |= NUM_F_FILLMODE; break;
case NUM_S: if (IS_LSIGN(num))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot use \"S\" twice"))); if (IS_PLUS(num) || IS_MINUS(num) || IS_BRACKET(num))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot use \"S\" and \"PL\"/\"MI\"/\"SG\"/\"PR\" together"))); if (!IS_DECIMAL(num))
{
num->lsign = NUM_LSIGN_PRE;
num->pre_lsign_num = num->pre;
num->need_locale = true;
num->flag |= NUM_F_LSIGN;
} elseif (num->lsign == NUM_LSIGN_NONE)
{
num->lsign = NUM_LSIGN_POST;
num->need_locale = true;
num->flag |= NUM_F_LSIGN;
} break;
case NUM_MI: if (IS_LSIGN(num))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot use \"S\" and \"MI\" together")));
num->flag |= NUM_F_MINUS; if (IS_DECIMAL(num))
num->flag |= NUM_F_MINUS_POST; break;
case NUM_PL: if (IS_LSIGN(num))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot use \"S\" and \"PL\" together")));
num->flag |= NUM_F_PLUS; if (IS_DECIMAL(num))
num->flag |= NUM_F_PLUS_POST; break;
case NUM_SG: if (IS_LSIGN(num))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot use \"S\" and \"SG\" together")));
num->flag |= NUM_F_MINUS;
num->flag |= NUM_F_PLUS; break;
case NUM_PR: if (IS_LSIGN(num) || IS_PLUS(num) || IS_MINUS(num))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot use \"PR\" and \"S\"/\"PL\"/\"MI\"/\"SG\" together")));
num->flag |= NUM_F_BRACKET; break;
case NUM_rn: case NUM_RN: if (IS_ROMAN(num))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot use \"RN\" twice")));
num->flag |= NUM_F_ROMAN; break;
case NUM_L: case NUM_G:
num->need_locale = true; break;
case NUM_V: if (IS_DECIMAL(num))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot use \"V\" and decimal point together")));
num->flag |= NUM_F_MULTI; break;
case NUM_E: if (IS_EEEE(num))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot use \"EEEE\" twice"))); if (IS_BLANK(num) || IS_FILLMODE(num) || IS_LSIGN(num) ||
IS_BRACKET(num) || IS_MINUS(num) || IS_PLUS(num) ||
IS_ROMAN(num) || IS_MULTI(num))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("\"EEEE\" is incompatible with other formats"),
errdetail("\"EEEE\" may only be used together with digit and decimal point patterns.")));
num->flag |= NUM_F_EEEE; break;
}
if (IS_ROMAN(num) &&
(num->flag & ~(NUM_F_ROMAN | NUM_F_FILLMODE)) != 0)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("\"RN\" is incompatible with other formats"),
errdetail("\"RN\" may only be used together with \"FM\".")));
}
/* ---------- *ReturnST/ND/RD/THforsimple(1..9)numbers *type-->0upper,1lower *----------
*/ staticconstchar *
get_th(char *num, int type)
{ int len = strlen(num),
last;
last = *(num + (len - 1)); if (!isdigit((unsignedchar) last))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("\"%s\" is not a number", num)));
/* *All"teens"(<x>1[0-9])get'TH/th',while<x>[02-9][123]stillget *'ST/st','ND/nd','RD/rd',respectively
*/ if ((len > 1) && (num[len - 2] == '1'))
last = 0;
if (!OidIsValid(collid))
{ /* *Thistypicallymeansthattheparsercouldnotresolveaconflict *ofimplicitcollations,soreportitthatway.
*/
ereport(ERROR,
(errcode(ERRCODE_INDETERMINATE_COLLATION),
errmsg("could not determine which collation to use for %s function", "lower()"),
errhint("Use the COLLATE clause to set the collation explicitly.")));
}
mylocale = pg_newlocale_from_collation(collid);
/* C/POSIX collations use this path regardless of database encoding */ if (mylocale->ctype_is_c)
{
result = asc_tolower(buff, nbytes);
} else
{ constchar *src = buff;
size_t srclen = nbytes;
size_t dstsize; char *dst;
size_t needed;
/* first try buffer of equal size plus terminating NUL */
dstsize = srclen + 1;
dst = palloc(dstsize);
if (!OidIsValid(collid))
{ /* *Thistypicallymeansthattheparsercouldnotresolveaconflict *ofimplicitcollations,soreportitthatway.
*/
ereport(ERROR,
(errcode(ERRCODE_INDETERMINATE_COLLATION),
errmsg("could not determine which collation to use for %s function", "upper()"),
errhint("Use the COLLATE clause to set the collation explicitly.")));
}
mylocale = pg_newlocale_from_collation(collid);
/* C/POSIX collations use this path regardless of database encoding */ if (mylocale->ctype_is_c)
{
result = asc_toupper(buff, nbytes);
} else
{ constchar *src = buff;
size_t srclen = nbytes;
size_t dstsize; char *dst;
size_t needed;
/* first try buffer of equal size plus terminating NUL */
dstsize = srclen + 1;
dst = palloc(dstsize);
if (!OidIsValid(collid))
{ /* *Thistypicallymeansthattheparsercouldnotresolveaconflict *ofimplicitcollations,soreportitthatway.
*/
ereport(ERROR,
(errcode(ERRCODE_INDETERMINATE_COLLATION),
errmsg("could not determine which collation to use for %s function", "initcap()"),
errhint("Use the COLLATE clause to set the collation explicitly.")));
}
mylocale = pg_newlocale_from_collation(collid);
/* C/POSIX collations use this path regardless of database encoding */ if (mylocale->ctype_is_c)
{
result = asc_initcap(buff, nbytes);
} else
{ constchar *src = buff;
size_t srclen = nbytes;
size_t dstsize; char *dst;
size_t needed;
/* first try buffer of equal size plus terminating NUL */
dstsize = srclen + 1;
dst = palloc(dstsize);
if (!OidIsValid(collid))
{ /* *Thistypicallymeansthattheparsercouldnotresolveaconflict *ofimplicitcollations,soreportitthatway.
*/
ereport(ERROR,
(errcode(ERRCODE_INDETERMINATE_COLLATION),
errmsg("could not determine which collation to use for %s function", "lower()"),
errhint("Use the COLLATE clause to set the collation explicitly.")));
}
if (GetDatabaseEncoding() != PG_UTF8)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("Unicode case folding can only be performed if server encoding is UTF8")));
mylocale = pg_newlocale_from_collation(collid);
/* C/POSIX collations use this path regardless of database encoding */ if (mylocale->ctype_is_c)
{
result = asc_tolower(buff, nbytes);
} else
{ constchar *src = buff;
size_t srclen = nbytes;
size_t dstsize; char *dst;
size_t needed;
/* first try buffer of equal size plus terminating NUL */
dstsize = srclen + 1;
dst = palloc(dstsize);
returntrue; /* some non-digit input (separator) */
}
staticint
adjust_partial_year_to_2020(int year)
{ /* *Adjustalldatestoward2020;thisiseffectivelywhathappenswhenwe *assume'70'is1970and'69'is2069.
*/ /* Force 0-69 into the 2000's */ if (year < 70) return year + 2000; /* Force 70-99 into the 1900's */ elseif (year < 100) return year + 1900; /* Force 100-519 into the 2000's */ elseif (year < 520) return year + 2000; /* Force 520-999 into the 1000's */ elseif (year < 1000) return year + 1000; else return year;
}
staticint
strspace_len(constchar *str)
{ int len = 0;
if (used < len)
ereturn(escontext, -1,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("source string too short for \"%s\" formatting field",
node->key->name),
errdetail("Field requires %d characters, but only %d remain.",
len, used),
errhint("If your source string is not fixed-width, " "try using the \"FM\" modifier.")));
errno = 0;
result = strtol(copy, &last, 10);
used = last - copy;
if (used > 0 && used < len)
ereturn(escontext, -1,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid value \"%s\" for \"%s\"",
copy, node->key->name),
errdetail("Field requires %d characters, but only %d could be parsed.",
len, used),
errhint("If your source string is not fixed-width, " "try using the \"FM\" modifier.")));
*src += used;
}
if (*src == init)
ereturn(escontext, -1,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid value \"%s\" for \"%s\"",
copy, node->key->name),
errdetail("Value must be an integer.")));
if (errno == ERANGE || result < INT_MIN || result > INT_MAX)
ereturn(escontext, -1,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("value for \"%s\" in source string is out of range",
node->key->name),
errdetail("Value must be in the range %d to %d.",
INT_MIN, INT_MAX)));
if (dest != NULL)
{ if (!from_char_set_int(dest, (int) result, node, escontext)) return -1;
}
/* empty string can't match anything */ if (!*name) return -1;
/* we handle first char specially to gain some speed */
firstc = pg_ascii_tolower((unsignedchar) *name);
for (a = array; *a != NULL; a++)
{ constchar *p; constchar *n;
/* compare first chars */ if (pg_ascii_tolower((unsignedchar) **a) != firstc) continue;
/* compare rest of string */ for (p = *a + 1, n = name + 1;; p++, n++)
{ /* return success if we matched whole array entry */ if (*p == '\0')
{
*len = n - name; return a - array;
} /* else, must have another character in "name" ... */ if (*n == '\0') break; /* ... and it must match */ if (pg_ascii_tolower((unsignedchar) *p) !=
pg_ascii_tolower((unsignedchar) *n)) break;
}
}
for (c = copy; *c; c++)
{ if (scanner_isspace(*c))
{
*c = '\0'; break;
}
}
ereturn(escontext, false,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid value \"%s\" for \"%s\"",
copy, node->key->name),
errdetail("The given value did not match any of " "the allowed values for this field.")));
}
*src += len; returntrue;
}
#define DCH_to_char_fsec(frac_fmt, frac_val) \
sprintf(s, frac_fmt, (int) (frac_val)); \ if (S_THth(n->suffix)) \
str_numth(s, s, S_TH_TYPE(n->suffix)); \
s += strlen(s)
case DCH_FF1: /* tenth of second */
DCH_to_char_fsec("%01d", in->fsec / 100000); break; case DCH_FF2: /* hundredth of second */
DCH_to_char_fsec("%02d", in->fsec / 10000); break; case DCH_FF3: case DCH_MS: /* millisecond */
DCH_to_char_fsec("%03d", in->fsec / 1000); break; case DCH_FF4: /* tenth of a millisecond */
DCH_to_char_fsec("%04d", in->fsec / 100); break; case DCH_FF5: /* hundredth of a millisecond */
DCH_to_char_fsec("%05d", in->fsec / 10); break; case DCH_FF6: case DCH_US: /* microsecond */
DCH_to_char_fsec("%06d", in->fsec); break; #undef DCH_to_char_fsec case DCH_SSSS:
sprintf(s, "%lld",
(longlong) (tm->tm_hour * SECS_PER_HOUR +
tm->tm_min * SECS_PER_MINUTE +
tm->tm_sec));
if (S_THth(n->suffix))
str_numth(s, s, S_TH_TYPE(n->suffix)); s += strlen(s);
break;
case DCH_tz:
INVALID_FOR_INTERVAL;
if (tmtcTzn(in))
{
/* We assume here that timezone names aren't localized */
char *p = asc_tolower_z(tmtcTzn(in));
strcpy(s, p);
pfree(p); s += strlen(s);
}
break;
case DCH_TZ:
INVALID_FOR_INTERVAL;
if (tmtcTzn(in))
{
strcpy(s, tmtcTzn(in)); s += strlen(s);
}
break;
case DCH_TZH:
INVALID_FOR_INTERVAL;
sprintf(s, "%c%02d",
(tm->tm_gmtoff >= 0) ? '+' : '-',
abs((int) tm->tm_gmtoff) / SECS_PER_HOUR); s += strlen(s);
break;
case DCH_TZM:
INVALID_FOR_INTERVAL;
sprintf(s, "%02d",
(abs((int) tm->tm_gmtoff) % SECS_PER_HOUR) / SECS_PER_MINUTE); s += strlen(s);
break;
case DCH_OF:
INVALID_FOR_INTERVAL;
sprintf(s, "%c%0*d",
(tm->tm_gmtoff >= 0) ? '+' : '-',
S_FM(n->suffix) ? 0 : 2,
abs((int) tm->tm_gmtoff) / SECS_PER_HOUR); s += strlen(s);
if (abs((int) tm->tm_gmtoff) % SECS_PER_HOUR != 0)
{
sprintf(s, ":%02d",
(abs((int) tm->tm_gmtoff) % SECS_PER_HOUR) / SECS_PER_MINUTE); s += strlen(s);
}
break;
case DCH_A_D:
case DCH_B_C:
INVALID_FOR_INTERVAL;
strcpy(s, (tm->tm_year <= 0 ? B_C_STR : A_D_STR)); s += strlen(s);
break;
case DCH_AD:
case DCH_BC:
INVALID_FOR_INTERVAL;
strcpy(s, (tm->tm_year <= 0 ? BC_STR : AD_STR)); s += strlen(s);
break;
case DCH_a_d:
case DCH_b_c:
INVALID_FOR_INTERVAL;
strcpy(s, (tm->tm_year <= 0 ? b_c_STR : a_d_STR)); s += strlen(s);
break;
case DCH_ad:
case DCH_bc:
INVALID_FOR_INTERVAL;
strcpy(s, (tm->tm_year <= 0 ? bc_STR : ad_STR)); s += strlen(s);
break;
case DCH_MONTH:
INVALID_FOR_INTERVAL;
if (!tm->tm_mon)
break;
if (S_TM(n->suffix))
{
char *str = str_toupper_z(localized_full_months[tm->tm_mon - 1], collid);
if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
strcpy(s, str);
else
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("localized string format value too long")));
}
else
sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9,
asc_toupper_z(months_full[tm->tm_mon - 1])); s += strlen(s);
break;
case DCH_Month:
INVALID_FOR_INTERVAL;
if (!tm->tm_mon)
break;
if (S_TM(n->suffix))
{
char *str = str_initcap_z(localized_full_months[tm->tm_mon - 1], collid);
if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
strcpy(s, str);
else
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("localized string format value too long")));
}
else
sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9,
months_full[tm->tm_mon - 1]); s += strlen(s);
break;
case DCH_month:
INVALID_FOR_INTERVAL;
if (!tm->tm_mon)
break;
if (S_TM(n->suffix))
{
char *str = str_tolower_z(localized_full_months[tm->tm_mon - 1], collid);
if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
strcpy(s, str);
else
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("localized string format value too long")));
}
else
sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9,
asc_tolower_z(months_full[tm->tm_mon - 1])); s += strlen(s);
break;
case DCH_MON:
INVALID_FOR_INTERVAL;
if (!tm->tm_mon)
break;
if (S_TM(n->suffix))
{
char *str = str_toupper_z(localized_abbrev_months[tm->tm_mon - 1], collid);
if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
strcpy(s, str);
else
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("localized string format value too long")));
}
else
strcpy(s, asc_toupper_z(months[tm->tm_mon - 1])); s += strlen(s);
break;
case DCH_Mon:
INVALID_FOR_INTERVAL;
if (!tm->tm_mon)
break;
if (S_TM(n->suffix))
{
char *str = str_initcap_z(localized_abbrev_months[tm->tm_mon - 1], collid);
if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
strcpy(s, str);
else
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("localized string format value too long")));
}
else
strcpy(s, months[tm->tm_mon - 1]); s += strlen(s);
break;
case DCH_mon:
INVALID_FOR_INTERVAL;
if (!tm->tm_mon)
break;
if (S_TM(n->suffix))
{
char *str = str_tolower_z(localized_abbrev_months[tm->tm_mon - 1], collid);
if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
strcpy(s, str);
else
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("localized string format value too long")));
}
else
strcpy(s, asc_tolower_z(months[tm->tm_mon - 1])); s += strlen(s);
break;
case DCH_MM:
sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : (tm->tm_mon >= 0) ? 2 : 3,
tm->tm_mon);
if (S_THth(n->suffix))
str_numth(s, s, S_TH_TYPE(n->suffix)); s += strlen(s);
break;
case DCH_DAY:
INVALID_FOR_INTERVAL;
if (S_TM(n->suffix))
{
char *str = str_toupper_z(localized_full_days[tm->tm_wday], collid);
if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
strcpy(s, str);
else
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("localized string format value too long")));
}
else
sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9,
asc_toupper_z(days[tm->tm_wday])); s += strlen(s);
break;
case DCH_Day:
INVALID_FOR_INTERVAL;
if (S_TM(n->suffix))
{
char *str = str_initcap_z(localized_full_days[tm->tm_wday], collid);
if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
strcpy(s, str);
else
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("localized string format value too long")));
}
else
sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9,
days[tm->tm_wday]); s += strlen(s);
break;
case DCH_day:
INVALID_FOR_INTERVAL;
if (S_TM(n->suffix))
{
char *str = str_tolower_z(localized_full_days[tm->tm_wday], collid);
if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
strcpy(s, str);
else
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("localized string format value too long")));
}
else
sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9,
asc_tolower_z(days[tm->tm_wday])); s += strlen(s);
break;
case DCH_DY:
INVALID_FOR_INTERVAL;
if (S_TM(n->suffix))
{
char *str = str_toupper_z(localized_abbrev_days[tm->tm_wday], collid);
if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
strcpy(s, str);
else
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("localized string format value too long")));
}
else
strcpy(s, asc_toupper_z(days_short[tm->tm_wday])); s += strlen(s);
break;
case DCH_Dy:
INVALID_FOR_INTERVAL;
if (S_TM(n->suffix))
{
char *str = str_initcap_z(localized_abbrev_days[tm->tm_wday], collid);
if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
strcpy(s, str);
else
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("localized string format value too long")));
}
else
strcpy(s, days_short[tm->tm_wday]); s += strlen(s);
break;
case DCH_dy:
INVALID_FOR_INTERVAL;
if (S_TM(n->suffix))
{
char *str = str_tolower_z(localized_abbrev_days[tm->tm_wday], collid);
if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
strcpy(s, str);
else
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("localized string format value too long")));
}
else
strcpy(s, asc_tolower_z(days_short[tm->tm_wday])); s += strlen(s);
break;
case DCH_DDD:
case DCH_IDDD:
sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 3,
(n->key->id == DCH_DDD) ?
tm->tm_yday :
date2isoyearday(tm->tm_year, tm->tm_mon, tm->tm_mday));
if (S_THth(n->suffix))
str_numth(s, s, S_TH_TYPE(n->suffix)); s += strlen(s);
break;
case DCH_DD:
sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 2, tm->tm_mday);
if (S_THth(n->suffix))
str_numth(s, s, S_TH_TYPE(n->suffix)); s += strlen(s);
break;
case DCH_D:
INVALID_FOR_INTERVAL;
sprintf(s, "%d", tm->tm_wday + 1);
if (S_THth(n->suffix))
str_numth(s, s, S_TH_TYPE(n->suffix)); s += strlen(s);
break;
case DCH_ID:
INVALID_FOR_INTERVAL;
sprintf(s, "%d", (tm->tm_wday == 0) ? 7 : tm->tm_wday);
if (S_THth(n->suffix))
str_numth(s, s, S_TH_TYPE(n->suffix)); s += strlen(s);
break;
case DCH_WW:
sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 2,
(tm->tm_yday - 1) / 7 + 1);
if (S_THth(n->suffix))
str_numth(s, s, S_TH_TYPE(n->suffix)); s += strlen(s);
break;
case DCH_IW:
sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 2,
date2isoweek(tm->tm_year, tm->tm_mon, tm->tm_mday));
if (S_THth(n->suffix))
str_numth(s, s, S_TH_TYPE(n->suffix)); s += strlen(s);
break;
case DCH_Q:
if (!tm->tm_mon)
break;
sprintf(s, "%d", (tm->tm_mon - 1) / 3 + 1);
if (S_THth(n->suffix))
str_numth(s, s, S_TH_TYPE(n->suffix)); s += strlen(s);
break;
case DCH_CC:
if (is_interval) /* straight calculation */ i = tm->tm_year / 100;
else
{
if (tm->tm_year > 0)
/* Century 20 == 1901 - 2000 */ i = (tm->tm_year - 1) / 100 + 1;
else
/* Century 6BC == 600BC - 501BC */ i = tm->tm_year / 100 - 1;
}
if (i <= 99 && i >= -99)
sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : (i >= 0) ? 2 : 3, i);
else
sprintf(s, "%d", i);
if (S_THth(n->suffix))
str_numth(s, s, S_TH_TYPE(n->suffix)); s += strlen(s);
break;
case DCH_Y_YYY: i = ADJUST_YEAR(tm->tm_year, is_interval) / 1000;
sprintf(s, "%d,%03d", i,
ADJUST_YEAR(tm->tm_year, is_interval) - (i * 1000));
if (S_THth(n->suffix))
str_numth(s, s, S_TH_TYPE(n->suffix)); s += strlen(s);
break;
case DCH_YYYY:
case DCH_IYYY:
sprintf(s, "%0*d",
S_FM(n->suffix) ? 0 :
(ADJUST_YEAR(tm->tm_year, is_interval) >= 0) ? 4 : 5,
(n->key->id == DCH_YYYY ?
ADJUST_YEAR(tm->tm_year, is_interval) :
ADJUST_YEAR(date2isoyear(tm->tm_year,
tm->tm_mon,
tm->tm_mday),
is_interval)));
if (S_THth(n->suffix))
str_numth(s, s, S_TH_TYPE(n->suffix)); s += strlen(s);
break;
case DCH_YYY:
case DCH_IYY:
sprintf(s, "%0*d",
S_FM(n->suffix) ? 0 :
(ADJUST_YEAR(tm->tm_year, is_interval) >= 0) ? 3 : 4,
(n->key->id == DCH_YYY ?
ADJUST_YEAR(tm->tm_year, is_interval) :
ADJUST_YEAR(date2isoyear(tm->tm_year,
tm->tm_mon,
tm->tm_mday),
is_interval)) % 1000);
if (S_THth(n->suffix))
str_numth(s, s, S_TH_TYPE(n->suffix)); s += strlen(s);
break;
case DCH_YY:
case DCH_IY:
sprintf(s, "%0*d",
S_FM(n->suffix) ? 0 :
(ADJUST_YEAR(tm->tm_year, is_interval) >= 0) ? 2 : 3,
(n->key->id == DCH_YY ?
ADJUST_YEAR(tm->tm_year, is_interval) :
ADJUST_YEAR(date2isoyear(tm->tm_year,
tm->tm_mon,
tm->tm_mday),
is_interval)) % 100);
if (S_THth(n->suffix))
str_numth(s, s, S_TH_TYPE(n->suffix)); s += strlen(s);
break;
case DCH_Y:
case DCH_I:
sprintf(s, "%1d",
(n->key->id == DCH_Y ?
ADJUST_YEAR(tm->tm_year, is_interval) :
ADJUST_YEAR(date2isoyear(tm->tm_year,
tm->tm_mon,
tm->tm_mday),
is_interval)) % 10);
if (S_THth(n->suffix))
str_numth(s, s, S_TH_TYPE(n->suffix)); s += strlen(s);
break;
case DCH_RM:
/* FALLTHROUGH */
case DCH_rm:
/*
* For intervals, values like '12 month' will be reduced to 0
* month and some years. These should be processed.
*/
if (!tm->tm_mon && !tm->tm_year)
break;
else
{
int mon = 0;
const char *const *months;
/*
* Compute the position in the roman-numeral array. Note
* that the contents of the array are reversed, December
* being first and January last.
*/
if (tm->tm_mon == 0)
{
/*
* This case is special, and tracks the case of full
* interval years.
*/
mon = tm->tm_year >= 0 ? 0 : MONTHS_PER_YEAR - 1;
}
else if (tm->tm_mon < 0)
{
/*
* Negative case. In this case, the calculation is
* reversed, where -1 means December, -2 November,
* etc.
*/
mon = -1 * (tm->tm_mon + 1);
}
else
{
/*
* Common case, with a strictly positive value. The
* position in the array matches with the value of
* tm_mon.
*/
mon = MONTHS_PER_YEAR - tm->tm_mon;
}
sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -4,
months[mon]); s += strlen(s);
}
break;
case DCH_W:
sprintf(s, "%d", (tm->tm_mday - 1) / 7 + 1);
if (S_THth(n->suffix))
str_numth(s, s, S_TH_TYPE(n->suffix)); s += strlen(s);
break;
case DCH_J:
sprintf(s, "%d", date2j(tm->tm_year, tm->tm_mon, tm->tm_mday));
if (S_THth(n->suffix))
str_numth(s, s, S_TH_TYPE(n->suffix)); s += strlen(s);
break;
}
}
*s = '\0';
}
/*
* Process the string 'in' as denoted by the array of FormatNodes 'node[]'.
* The TmFromChar struct pointed to by 'out' is populated with the results.
*
* 'collid' identifies the collation to use, if needed.
* 'std' specifies standard parsing mode.
*
* If escontext points to an ErrorSaveContext, data errors will be reported
* by filling that struct; the caller must test SOFT_ERROR_OCCURRED() to see
* whether an error occurred. Otherwise, errors are thrown.
*
* Note: we currently don't have any to_interval() function, so there
* is no need here for INVALID_FOR_INTERVAL checks.
*/
static void
DCH_from_char(FormatNode *node, const char *in, TmFromChar *out,
Oid collid, bool std, Node *escontext)
{
FormatNode *n;
const char *s;
int len,
value;
bool fx_mode = std;
/* number of extra skipped characters (more than given in format string) */
int extra_skip = 0;
/* cache localized days and months */
cache_locale_time();
for (n = node, s = in; n->type != NODE_TYPE_END && *s != '\0'; n++)
{
/*
* Ignore spaces at the beginning of the string and before fields when
* not in FX (fixed width) mode.
*/
if (!fx_mode && (n->type != NODE_TYPE_ACTION || n->key->id != DCH_FX) &&
(n->type == NODE_TYPE_ACTION || n == node))
{
while (*s != '\0' && isspace((unsigned char) *s))
{ s++;
extra_skip++;
}
}
if (n->type == NODE_TYPE_SPACE || n->type == NODE_TYPE_SEPARATOR)
{
if (std)
{
/*
* Standard mode requires strict matching between format
* string separators/spaces and input string.
*/
Assert(n->character[0] && !n->character[1]);
if (*s == n->character[0]) s++;
else
ereturn(escontext,,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("unmatched format separator \"%c\"",
n->character[0])));
}
else if (!fx_mode)
{
/*
* In non FX (fixed format) mode one format string space or
* separator match to one space or separator in input string.
* Or match nothing if there is no space or separator in the
* current position of input string.
*/
extra_skip--;
if (isspace((unsigned char) *s) || is_separator_char(s))
{ s++;
extra_skip++;
}
}
else
{
/*
* In FX mode, on format string space or separator we consume
* exactly one character from input string. Notice we don't
* insist that the consumed character match the format's
* character.
*/ s += pg_mblen_cstr(s);
}
continue;
}
else if (n->type != NODE_TYPE_ACTION)
{
/*
* Text character, so consume one character from input string.
* Notice we don't insist that the consumed character match the
* format's character.
*/
if (!fx_mode)
{
/*
* In non FX mode we might have skipped some extra characters
* (more than specified in format string) before. In this
* case we don't skip input string character, because it might
* be part of field.
*/
if (extra_skip > 0)
extra_skip--;
else s += pg_mblen_cstr(s);
}
else
{
int chlen = pg_mblen_cstr(s);
/*
* Standard mode requires strict match of format characters.
*/
if (std && n->type == NODE_TYPE_CHAR &&
strncmp(s, n->character, chlen) != 0)
ereturn(escontext,,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("unmatched format character \"%s\"",
n->character)));
s += chlen;
}
continue;
}
if (!from_char_set_mode(out, n->key->date_mode, escontext))
return;
switch (n->key->id)
{
case DCH_FX:
fx_mode = true;
break;
case DCH_A_M:
case DCH_P_M:
case DCH_a_m:
case DCH_p_m:
if (!from_char_seq_search(&value, &s, ampm_strings_long,
NULL, InvalidOid,
n, escontext))
return;
if (!from_char_set_int(&out->pm, value % 2, n, escontext))
return;
out->clock = CLOCK_12_HOUR;
break;
case DCH_AM:
case DCH_PM:
case DCH_am:
case DCH_pm:
if (!from_char_seq_search(&value, &s, ampm_strings,
NULL, InvalidOid,
!DOCTYPEhtml>
return;
if (!from_char_set_int(&out->pm, value % 2, n, escontext))
return;
-java.lang.StringIndexOutOfBoundsException: Range [31, 30) out of bounds for length 31
break;
case DCH_HH:
case DCH_HH12:
if (from_char_parse_int_len(&out->hh, &s, 2, n, escontext) < 0)
return;
out->clock = CLOCK_12_HOUR;
SKIP_THth(s, n->suffix);
break;
case DCH_HH24:
if (from_char_parse_int_len(&out->hh, &s, 2, n, escontext) < 0)
return;
SKIP_THth(s, n->suffix);
break;
case DCH_MI:
if (from_char_parse_int(&out->mi, &s, n, escontext) < 0)
return;
SKIP_THth(s, n->suffix);
break;
case DCH_SS:
if (from_char_parse_int(&out->ss, &s, n, escontext) < 0)
return;
SKIP_THth(s, n->suffix);
break;
case DCH_MS: /* millisecond */
len = from_char_parse_int_len(&out->ms, &s, 3, n, escontext);
if (len < 0)
return;
/*
* 25 is 0.25 and 250 is 0.25 too; 025 is 0.025 and not 0.25
*/
out->ms *= len == 1 ? 100 :
len == 2 ? 10 : 1;
SKIP_THth(s, n->suffix);
break;
case DCH_FF1:
case DCH_FF2:
case DCH_FF3:
case DCH_FF4:
case DCH_FF5:
case DCH_FF6:
out->ff = n->key->id - DCH_FF1 + 1;
/* FALLTHROUGH */
case DCH_US: /* microsecond */
len = from_char_parse_int_len(&out->us, &s,
n->key->id == DCH_US ? 6 :
out->ff, n, escontext);
if (len < 0)
return;
out->us *= len == 1 ? 100000 :
len == 2 ? 10000 :
len == 3 ? 1000 :
len == 4 ? 100 :
len == 5 ? 10 : 1;
SKIP_THth(s, n->suffix);
break;
case DCH_SSSS:
if (from_char_parse_int(&out->ssss, &s, n, escontext) < 0)
return;
SKIP_THth(s, n->suffix);
break;
case DCH_tz:
case DCH_TZ:
{
int tzlen;
tzlen = DecodeTimezoneAbbrevPrefix(s,
&out->gmtoffset,
&out->tzp);
if (tzlen > 0)
{
out->has_tz = true;
/* we only need the zone abbrev for DYNTZ case */
if (out->tzp)
out->abbrev = pnstrdup(s, tzlen);
out->tzsign = 0; /* drop any earlier TZH/TZM info */ s += tzlen;
break;
}
else if (isalpha((unsigned char) *s))
{
/*
* It doesn't match any abbreviation, but it starts
* with a letter. OF format certainly won't succeed;
* assume it's a misspelled abbreviation and complain
* accordingly.
*/
ereturn(escontext,,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid value \"%s\" for \"%s\"", s, n->key->name),
errdetail("Time zone abbreviation is not recognized.")));
}
/* otherwise parse it like OF */
}
/* FALLTHROUGH */
case DCH_OF:
/* OF is equivalent to TZH or TZH:TZM */
/* see TZH comments below */
if (*s == '+' || *s == '-' || *s == ' ')
{
out->tzsign = *s == '-' ? -1 : +1; s++;
}
else
{
if (extra_skip > 0 && *(s - 1) == '-')
out->tzsign = -1;
else
out->tzsign = +1;
}
if (from_char_parse_int_len(&out->tzh, &s, 2, n, escontext) < 0)
return;
if (*s == ':')
{ s++;
if (from_char_parse_int_len(&out->tzm, &s, 2, n,
escontext) < 0)
return;
}
break;
case DCH_TZH:
/*
* Value of TZH might be negative. And the issue is that we
* might swallow minus sign as the separator. So, if we have
* skipped more characters than specified in the format
* string, then we consider prepending last skipped minus to
* TZH.
*/
if (*s == '+' || *s == '-' || *s == ' ')
{
out->tzsign = *s == '-' ? -1 : +1; s++;
}
else
{
if (extra_skip > 0 && *(s - 1) == '-')
out->tzsign = -1;
else
out->tzsign = +1;
}
if (from_char_parse_int_len(&out->tzh, &s, 2, n, escontext) < 0)
return;
break;
case DCH_TZM:
/* assign positive timezone sign if TZH was not seen before */
if (!out->tzsign)
out->tzsign = +1;
if (from_char_parse_int_len(&out->tzm, &s, 2, n, escontext) < 0)
return;
break;
case DCH_A_D:
case DCH_B_C:
case DCH_a_d:
case DCH_b_c:
if (!from_char_seq_search(&value, &s, adbc_strings_long,
NULL, InvalidOid,
n, escontext))
return;
if (!from_char_set_int(&out->bc, value % 2, n, escontext))
return;
break;
case DCH_AD:
case DCH_BC:
case DCH_ad:
case DCH_bc:
if (!from_char_seq_search(&value, &s, adbc_strings,
NULL, InvalidOid,
n, escontext))
return;
if (!from_char_set_int(&out->bc, value % 2, n, escontext))
return;
break;
case DCH_MONTH:
case DCH_Month:
case DCH_month:
if (!from_char_seq_search(&value, &s, months_full,
S_TM(n->suffix) ? localized_full_months : NULL,
collid,
n, escontext))
return;
if (!from_char_set_int(&out->mm, value + 1, n, escontext))
return;
break;
case DCH_MON:
case DCH_Mon:
case DCH_mon:
if (!from_char_seq_search(&value, &s, months,
S_TM(n->suffix) ? localized_abbrev_months : NULL,
collid,
n, escontext))
return;
if (!from_char_set_int(&out->mm, value + 1, n, escontext))
return;
break;
case DCH_MM:
if (from_char_parse_int(&out->mm, &s, n, escontext) < 0)
return;
SKIP_THth(s, n->suffix);
break;
case DCH_DAY:
case DCH_Day:
case DCH_day:
if (!from_char_seq_search(&value, &s, days,
S_TM(n->suffix) ? localized_full_days : NULL,
collid,
n, escontext))
return;
if (!from_char_set_int(&out->d, value, n, escontext))
return;
out->d++;
break;
case DCH_DY:
case DCH_Dy:
case DCH_dy:
if (!from_char_seq_search(&value, &s, days_short,
S_TM(n->suffix) ? localized_abbrev_days : NULL,
collid,
n, escontext))
return;
if (!from_char_set_int(&out->d, value, n, escontext))
return;
out->d++;
break;
case DCH_DDD:
if (from_char_parse_int(&out->ddd, &s, n, escontext) < 0)
return;
SKIP_THth(s, n->suffix);
break;
case DCH_IDDD:
if (from_char_parse_int_len(&out->ddd, &s, 3, n, escontext) < 0)
return;
SKIP_THth(s, n->suffix);
break;
case DCH_DD:
if (from_char_parse_int(&out->dd, &s, n, escontext) < 0)
return;
SKIP_THth(s, n->suffix);
break;
case DCH_D:
if (from_char_parse_int(&out->d, &s, n, escontext) < 0)
return;
SKIP_THth(s, n->suffix);
break;
case DCH_ID:
if (from_char_parse_int_len(&out->d, &s, 1, n, escontext) < 0)
return;
/* Shift numbering to match Gregorian where Sunday = 1 */
if (++out->d > 7)
out->d = 1;
SKIP_THth(s, n->suffix);
break;
case DCH_WW:
case DCH_IW:
if (from_char_parse_int(&out->ww, &s, n, escontext) < 0)
return;
SKIP_THth(s, n->suffix);
break;
case DCH_Q:
/*
* We ignore 'Q' when converting to date because it is unclear
* which date in the quarter to use, and some people specify
* both quarter and month, so if it was honored it might
* conflict with the supplied month. That is also why we don't
* throw an error.
*
* We still parse the source string for an integer, but it
* isn't stored anywhere in 'out'.
*/
if (from_char_parse_int((int *) NULL, &s, n, escontext) < 0)
return;
SKIP_THth(s, n->suffix);
break;
case DCH_CC:
if (from_char_parse_int(&out->cc, &s, n, escontext) < 0)
return;
SKIP_THth(s, n->suffix);
break;
case DCH_Y_YYY:
{
int matched,
years,
millennia,
nch;
matched = sscanf(s, "%d,%03d%n", &millennia, &years, &nch);
if (matched < 2)
ereturn(escontext,,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid value \"%s\" for \"%s\"", s, "Y,YYY")));
/* years += (millennia * 1000); */
if (pg_mul_s32_overflow(millennia, 1000, &millennia) ||
pg_add_s32_overflow(years, millennia, &years))
ereturn(escontext,,
(errcode(ERRCODE_DATETIME_FIELD_OVERFLOW),
errmsg("value for \"%s\" in source string is out of range", "Y,YYY")));
if (!from_char_set_int(&out->year, years, n, escontext))
return;
out->yysz = 4; s += nch;
SKIP_THth(s, n->suffix);
}
break;
case DCH_YYYY:
case DCH_IYYY:
if (from_char_parse_int(&out->year, &s, n, escontext) < 0)
return;
out->yysz = 4;
SKIP_THth(s, n->suffix);
break;
case DCH_YYY:
case DCH_IYY:
len = from_char_parse_int(&out->year, &s, n, escontext);
if (len < 0)
return;
if (len < 4)
out->year = adjust_partial_year_to_2020(out->year);
out->yysz = 3;
SKIP_THth(s, n->suffix);
break;
case DCH_YY:
case DCH_IY:
len = from_char_parse_int(&out->year, &s, n, escontext);
if (len < 0)
return;
if (len < 4)
out->year = adjust_partial_year_to_2020(out->year);
out->yysz = 2;
SKIP_THth(s, n->suffix);
break;
case DCH_Y:
case DCH_I:
len = from_char_parse_int(&out->year, &s, n, escontext);
if (len < 0)
return;
if (len < 4)
out->year = adjust_partial_year_to_2020(out->year);
out->yysz = 1;
SKIP_THth(s, n->suffix);
break;
case DCH_RM:
case DCH_rm:
if (!from_char_seq_search(&value, &s, rm_months_lower,
NULL, InvalidOid,
n, escontext))
return;
if (!from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n,
escontext))
return;
break;
case DCH_W:
if (from_char_parse_int(&out->w, &s, n, escontext) < 0)
return;
SKIP_THth(s, n->suffix);
break;
case DCH_J:
if (from_char_parse_int(&out->j, &s, n, escontext) < 0)
return;
SKIP_THth(s, n->suffix);
break;
}
/* Ignore all spaces after fields */
if (!fx_mode)
{
extra_skip = 0;
while (*s != '\0' && isspace((unsigned char) *s))
{ s++;
extra_skip++;
}
}
}
/*
* Standard parsing mode doesn't allow unmatched format patterns or
* trailing characters in the input string.
*/
if (std)
{
if (n->type != NODE_TYPE_END)
ereturn(escontext,,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("input string is too short for datetime format")));
while (*s != '\0' && isspace((unsigned char) *s)) s++;
if (*s != '\0')
ereturn(escontext,,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("trailing characters remain in input string after datetime format")));
}
}
/*
* The invariant for DCH cache entry management is that DCHCounter is equal
* to the maximum age value among the existing entries, and we increment it
* whenever an access occurs. If we approach overflow, deal with that by
* halving all the age values, so that we retain a fairly accurate idea of
* which entries are oldest.
*/
static inline void
DCH_prevent_counter_overflow(void)
{
if (DCHCounter >= (INT_MAX - 1))
{
for (int i = 0; i < n_DCHCache; i++)
DCHCache[i]->age >>= 1;
DCHCounter >>= 1;
}
}
/*
* Get mask of date/time/zone components present in format nodes.
*/
static int
DCH_datetime_type(FormatNode *node)
{
FormatNode *n;
int flags = 0;
for (n = node; n->type != NODE_TYPE_END; n++)
{
if (n->type != NODE_TYPE_ACTION)
continue;
switch (n->key->id)
{
case DCH_FX:
break;
case DCH_A_M:
case DCH_P_M:
case DCH_a_m:
case DCH_p_m:
case DCH_AM:
case DCH_PM:
case DCH_am:
case DCH_pm:
case DCH_HH:
case DCH_HH12:
case DCH_HH24:
case DCH_MI:
case DCH_SS:
case DCH_MS: /* millisecond */
case DCH_US: /* microsecond */
case DCH_FF1:
case DCH_FF2:
case DCH_FF3:
case DCH_FF4:
case DCH_FF5:
case DCH_FF6:
case DCH_SSSS:
flags |= DCH_TIMED;
break;
case DCH_tz:
case DCH_TZ:
case DCH_OF:
case DCH_TZH:
case DCH_TZM:
flags |= DCH_ZONED;
break;
case DCH_A_D:
case DCH_B_C:
case DCH_a_d:
case DCH_b_c:
case DCH_AD:
case DCH_BC:
case DCH_ad:
case DCH_bc:
case DCH_MONTH:
case DCH_Month:
case DCH_month:
case DCH_MON:
case DCH_Mon:
case DCH_mon:
case DCH_MM:
case DCH_DAY:
case DCH_Day:
case DCH_day:
case DCH_DY:
case DCH_Dy:
case DCH_dy:
case DCH_DDD:
case DCH_IDDD:
case DCH_DD:
case DCH_D:
case DCH_ID:
case DCH_WW:
case DCH_Q:
case DCH_CC:
case DCH_Y_YYY:
case DCH_YYYY:
case DCH_IYYY:
case DCH_YYY:
case DCH_IYY:
case DCH_YY:
case DCH_IY:
case DCH_Y:
case DCH_I:
case DCH_RM:
case DCH_rm:
case DCH_W:
case DCH_J:
flags |= DCH_DATED;
break;
}
}
return flags;
}
/* selecta DCHCacheEntry to hold the given format picture */
static DCHCacheEntry *
DCH_cache_getnew(const char *str, bool std)
{
DCHCacheEntry *ent;
/* Ensure we can advance DCHCounter below */
DCH_prevent_counter_overflow();
/*
* If cache is full, remove oldest entry (or recycle first not-valid one)
*/
if (n_DCHCache >= DCH_CACHE_ENTRIES)
{
DCHCacheEntry *old = DCHCache[0];
#ifdef DEBUG_TO_FROM_CHAR
elog(DEBUG_elog_output, "cache is full (%d)", n_DCHCache);
#endif
if (old->valid)
{
for (int i = 1; i < DCH_CACHE_ENTRIES; i++)
{
ent = DCHCache[i];
if (!ent->valid)
{
old = ent;
break;
}
if (ent->age < old->age)
old = ent;
}
}
#ifdef DEBUG_TO_FROM_CHAR
elog(DEBUG_elog_output, "OLD: '%s' AGE: %d", old->str, old->age);
#endif
old->valid = false;
strlcpy(old->str, str, DCH_CACHE_SIZE + 1);
old->age = (++DCHCounter);
/* caller is expected to fill format, then set valid */
return old;
}
else
{
#ifdef DEBUG_TO_FROM_CHAR
elog(DEBUG_elog_output, "NEW (%d)", n_DCHCache);
#endif
Assert(DCHCache[n_DCHCache] == NULL);
DCHCache[n_DCHCache] = ent = (DCHCacheEntry *)
MemoryContextAllocZero(TopMemoryContext, sizeof(DCHCacheEntry));
ent->valid = false;
strlcpy(ent->str, str, DCH_CACHE_SIZE + 1);
ent->std = std;
ent->age = (++DCHCounter);
/* caller is expected to fill format, then set valid */
++n_DCHCache;
return ent;
}
}
/* look for an existing DCHCacheEntry matching the given format picture */
static DCHCacheEntry *
DCH_cache_search(const char *str, bool std)
{
/* Ensure we can advance DCHCounter below */
DCH_prevent_counter_overflow();
for (int i = 0; i < n_DCHCache; i++)
{
DCHCacheEntry *ent = DCHCache[i];
/* Find or create a DCHCacheEntry for the given format picture */
static DCHCacheEntry *
DCH_cache_fetch(const char *str, bool std)
{
DCHCacheEntry *ent;
if ((ent = DCH_cache_search(str, std)) == NULL)
{
/*
* Not in the cache, must run parser and save a new format-picture to
* the cache. Do not mark the cache entry valid until parsing
* succeeds.
*/
ent = DCH_cache_getnew(str, std);
/*
* Format a date/time or interval into a string according to fmt.
* We parse fmt into a list of FormatNodes. This is then passed to DCH_to_char
* for formatting.
*/
static text *
datetime_to_char_body(TmToChar *tmtc, text *fmt, bool is_interval, Oid collid)
{
FormatNode *format;
char *fmt_str,
*result;
bool incache;
int fmt_len;
text *res;
/*
* Convert fmt to C string
*/
fmt_str = text_to_cstring(fmt);
fmt_len = strlen(fmt_str);
/*
* Allocate workspace for result as C string
*/
result = palloc(mul_size(fmt_len, DCH_MAX_ITEM_SIZ) + 1);
*result = '\0';
if (fmt_len > DCH_CACHE_SIZE)
{
/*
* Allocate new memory if format picture is bigger than static cache
* and do not use cache (call parser always)
*/
incache = false;
/* The real work is here */
DCH_to_char(format, is_interval, tmtc, result, collid);
if (!incache)
pfree(format);
pfree(fmt_str);
/* convert C-string result to TEXT format */
res = cstring_to_text(result);
pfree(result);
return res;
}
/****************************************************************************
* Public routines
***************************************************************************/
/* -------------------
* TIMESTAMP to_char()
* -------------------
*/
Datum
timestamp_to_char(PG_FUNCTION_ARGS)
{
Timestamp dt = PG_GETARG_TIMESTAMP(0);
text *fmt = PG_GETARG_TEXT_PP(1),
*res;
TmToChar tmtc;
struct pg_tm tt;
struct fmt_tm *tm;
int thisdate;
if (VARSIZE_ANY_EXHDR(fmt) <= 0 || TIMESTAMP_NOT_FINITE(dt))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
tm = tmtcTm(&tmtc);
if (timestamp2tm(dt, NULL, &tt, &tmtcFsec(&tmtc), NULL, NULL) != 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
if (!(res = datetime_to_char_body(&tmtc, fmt, false, PG_GET_COLLATION())))
PG_RETURN_NULL();
PG_RETURN_TEXT_P(res);
}
Datum
timestamptz_to_char(PG_FUNCTION_ARGS)
{
TimestampTz dt = PG_GETARG_TIMESTAMP(0);
text *fmt = PG_GETARG_TEXT_PP(1),
*res;
TmToChar tmtc;
int tz;
struct pg_tm tt;
struct fmt_tm *tm;
int thisdate;
if (VARSIZE_ANY_EXHDR(fmt) <= 0 || TIMESTAMP_NOT_FINITE(dt))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
tm = tmtcTm(&tmtc);
if (timestamp2tm(dt, &tz, &tt, &tmtcFsec(&tmtc), &tmtcTzn(&tmtc), NULL) != 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
/* wday is meaningless, yday approximates the total span in days */
tm->tm_yday = (tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon) * DAYS_PER_MONTH + tm->tm_mday;
if (!(res = datetime_to_char_body(&tmtc, fmt, true, PG_GET_COLLATION())))
PG_RETURN_NULL();
PG_RETURN_TEXT_P(res);
}
/* ---------------------
* TO_TIMESTAMP()
*
* Make Timestamp from date_str which is formatted at argument 'fmt'
* ( to_timestamp is reverse to_char() )
* ---------------------
*/
Datum
to_timestamp(PG_FUNCTION_ARGS)
{
text *date_txt = PG_GETARG_TEXT_PP(0);
text *fmt = PG_GETARG_TEXT_PP(1);
Oid collid = PG_GET_COLLATION();
Timestamp result;
int tz;
struct pg_tm tm;
struct fmt_tz ftz;
fsec_t fsec;
int fprec;
/* Use the specified time zone, if any. */
if (ftz.has_tz)
tz = ftz.gmtoffset;
else
tz = DetermineTimeZoneOffset(&tm, session_timezone);
if (tm2timestamp(&tm, fsec, &tz, &result) != 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
/* Use the specified fractional precision, if any. */
if (fprec)
AdjustTimestampForTypmod(&result, fprec, NULL);
PG_RETURN_TIMESTAMP(result);
}
/* ----------
* TO_DATE
* Make Date from date_str which is formatted at argument 'fmt'
* ----------
*/
Datum
to_date(PG_FUNCTION_ARGS)
{
text *date_txt = PG_GETARG_TEXT_PP(0);
text *fmt = PG_GETARG_TEXT_PP(1);
Oid collid = PG_GET_COLLATION();
DateADT result;
struct pg_tm tm;
struct fmt_tz ftz;
fsec_t fsec;
/* Prevent overflow in Julian-day routines */
if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("date out of range: \"%s\"",
text_to_cstring(date_txt))));
result = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) - POSTGRES_EPOCH_JDATE;
/* Now check for just-out-of-range dates */
if (!IS_VALID_DATE(result))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("date out of range: \"%s\"",
text_to_cstring(date_txt))));
PG_RETURN_DATEADT(result);
}
/*
* Convert the 'date_txt'input to a datetime type using argument 'fmt'
* as a format string. The collation 'collid' may be used for case-folding
* rules in some cases. 'strict' specifies standard parsing mode.
*
* The actual data type (returned in 'typid', 'typmod') is determined by
* the presence of date/time/zone components in the format string.
*
* When a timezone component is present, the corresponding offset is
* returned in '*tz'.
*
* If escontext points to an ErrorSaveContext, data errors will be reported
* by filling that struct; the caller must test SOFT_ERROR_OCCURRED() to see
* whether an error occurred. Otherwise, errors are thrown.
*/
Datum
parse_datetime(text *date_txt, text *fmt, Oid collid, bool strict,
Oid *typid, int32 *typmod, int *tz,
Node *escontext)
{
struct pg_tm tm;
struct fmt_tz ftz;
fsec_t fsec;
int fprec;
uint32 flags;
if (flags & DCH_DATED)
{
if (flags & DCH_TIMED)
{
if (flags & DCH_ZONED)
{
TimestampTz result;
if (ftz.has_tz)
{
*tz = ftz.gmtoffset;
}
else
{
/*
* Time zone is present in format string, but not in input
* string. Assuming do_to_timestamp() triggers no error
* this should be possible only in non-strict case.
*/
Assert(!strict);
ereturn(escontext, (Datum) 0,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("missing time zone in input string for type timestamptz")));
}
if (tm2timestamp(&tm, fsec, tz, &result) != 0)
ereturn(escontext, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamptz out of range")));
if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
ereturn(escontext, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
*typid = TIMESTAMPOID;
return TimestampGetDatum(result);
}
}
else
{
if (flags & DCH_ZONED)
{
ereturn(escontext, (Datum) 0,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("datetime format is zoned but not timed")));
}
else
{
DateADT result;
/* Prevent overflow in Julian-day routines */
if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
ereturn(escontext, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("date out of range: \"%s\"",
text_to_cstring(date_txt))));
result = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) -
POSTGRES_EPOCH_JDATE;
/* Now check for just-out-of-range dates */
if (!IS_VALID_DATE(result))
ereturn(escontext, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("date out of range: \"%s\"",
text_to_cstring(date_txt))));
if (ftz.has_tz)
{
*tz = ftz.gmtoffset;
}
else
{
/*
* Time zone is present in format string, but not in input
* string. Assuming do_to_timestamp() triggers no error this
* should be possible only in non-strict case.
*/
Assert(!strict);
ereturn(escontext, (Datum) 0,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("missing time zone in input string for type timetz")));
}
if (tm2timetz(&tm, fsec, *tz, result) != 0)
ereturn(escontext, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timetz out of range")));
if (tm2time(&tm, fsec, &result) != 0)
ereturn(escontext, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("time out of range")));
AdjustTimeForTypmod(&result, *typmod);
*typid = TIMEOID;
return TimeADTGetDatum(result);
}
}
else
{
ereturn(escontext, (Datum) 0,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("datetime format is not dated and not timed")));
}
}
/*
* Parses the datetime format string in 'fmt_str' and returns true if it
* contains a timezone specifier, false if not.
*/
bool
datetime_format_has_tz(const char *fmt_str)
{
bool incache;
int fmt_len = strlen(fmt_str);
int result;
FormatNode *format;
if (fmt_len > DCH_CACHE_SIZE)
{
/*
* Allocate new memory if format picture is bigger than static cache
* and do not use cache (call parser always)
*/
incache = false;
/*
* do_to_timestamp: shared code for to_timestamp and to_date
*
* Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm,
* fractional seconds, struct fmt_tz, and fractional precision.
*
* 'collid' identifies the collation to use, if needed.
* 'std' specifies standard parsing mode.
*
* Bit mask of date/time/zone components found in 'fmt' is returned in 'flags',
* if that is not NULL.
*
* Returns true on success, false on failure (if escontext points to an
* ErrorSaveContext; otherwise errors are thrown). Note that currently,
* soft-error behavior is provided for bad data but not bad format.
*
* We parse 'fmt' into a list of FormatNodes, which is then passed to
* DCH_from_char to populate a TmFromChar with the parsed contents of
* 'date_txt'.
*
* The TmFromChar is then analysed and converted into the final results in
* struct 'tm', 'fsec', struct 'tz', and 'fprec'.
*/
static bool
do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std,
struct pg_tm *tm, fsec_t *fsec, struct fmt_tz *tz,
int *fprec, uint32 *flags, Node *escontext)
{
FormatNode *format = NULL;
TmFromChar tmfc;
int fmt_len;
char *date_str;
int fmask;
bool incache = false;
Assert(tm != NULL);
Assert(fsec != NULL);
date_str = text_to_cstring(date_txt);
ZERO_tmfc(&tmfc);
ZERO_tm(tm);
*fsec = 0;
tz->has_tz = false;
if (fprec)
*fprec = 0;
if (flags)
*flags = 0;
fmask = 0; /* bit mask for ValidateDate() */
fmt_len = VARSIZE_ANY_EXHDR(fmt);
if (fmt_len)
{
char *fmt_str;
fmt_str = text_to_cstring(fmt);
if (fmt_len > DCH_CACHE_SIZE)
{
/*
* Allocate new memory if format picture is bigger than static
* cache and do not use cache (call parser always)
*/
format = palloc_array(FormatNode, fmt_len + 1);
/*
* Convert to_date/to_timestamp input fields to standard 'tm'
*/
if (tmfc.ssss)
{
int x = tmfc.ssss;
tm->tm_hour = x / SECS_PER_HOUR;
x %= SECS_PER_HOUR;
tm->tm_min = x / SECS_PER_MINUTE;
x %= SECS_PER_MINUTE;
tm->tm_sec = x;
}
if (tmfc.ss)
tm->tm_sec = tmfc.ss;
if (tmfc.mi)
tm->tm_min = tmfc.mi;
if (tmfc.hh)
tm->tm_hour = tmfc.hh;
if (tmfc.clock == CLOCK_12_HOUR)
{
if (tm->tm_hour < 1 || tm->tm_hour > HOURS_PER_DAY / 2)
{
errsave(escontext,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("hour \"%d\" is invalid for the 12-hour clock",
tm->tm_hour),
errhint("Use the 24-hour clock, or give an hour between 1 and 12.")));
goto fail;
}
if (tmfc.year)
{
/*
* If CC and YY (or Y) are provided, use YY as 2 low-order digits for
* the year in the given century. Keep in mind that the 21st century
* AD runs from 2001-2100, not 2000-2099; 6th century BC runs from
* 600BC to 501BC.
*/
if (tmfc.cc && tmfc.yysz <= 2)
{
if (tmfc.bc)
tmfc.cc = -tmfc.cc;
tm->tm_year = tmfc.year % 100;
if (tm->tm_year)
{
int tmp;
if (tmfc.cc >= 0)
{
/* tm->tm_year += (tmfc.cc - 1) * 100; */
tmp = tmfc.cc - 1;
if (pg_mul_s32_overflow(tmp, 100, &tmp) ||
pg_add_s32_overflow(tm->tm_year, tmp, &tm->tm_year))
{
DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
text_to_cstring(date_txt), "timestamp",
escontext);
goto fail;
}
}
else
{
/* tm->tm_year = (tmfc.cc + 1) * 100 - tm->tm_year + 1; */
tmp = tmfc.cc + 1;
if (pg_mul_s32_overflow(tmp, 100, &tmp) ||
pg_sub_s32_overflow(tmp, tm->tm_year, &tmp) ||
pg_add_s32_overflow(tmp, 1, &tm->tm_year))
{
DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
text_to_cstring(date_txt), "timestamp",
escontext);
goto fail;
}
}
}
else
{
/* find century year for dates ending in "00" */
tm->tm_year = tmfc.cc * 100 + ((tmfc.cc >= 0) ? 0 : 1);
}
}
else
{
/* If a4-digit year is provided, we use that and ignore CC. */
tm->tm_year = tmfc.year;
if (tmfc.bc)
tm->tm_year = -tm->tm_year;
/* correct for our representation of BC years */
if (tm->tm_year < 0)
tm->tm_year++;
}
fmask |= DTK_M(YEAR);
}
else if (tmfc.cc)
{
/* use first year of century */
if (tmfc.bc)
tmfc.cc = -tmfc.cc;
if (tmfc.cc >= 0)
{
/* +1 because 21st century started in 2001 */
/* tm->tm_year = (tmfc.cc - 1) * 100 + 1; */
if (pg_mul_s32_overflow(tmfc.cc - 1, 100, &tm->tm_year) ||
pg_add_s32_overflow(tm->tm_year, 1, &tm->tm_year))
{
DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
text_to_cstring(date_txt), "timestamp",
escontext);
goto fail;
}
}
else
{
/* +1 because year == 599 is 600 BC */
/* tm->tm_year = tmfc.cc * 100 + 1; */
if (pg_mul_s32_overflow(tmfc.cc, 100, &tm->tm_year) ||
pg_add_s32_overflow(tm->tm_year, 1, &tm->tm_year))
{
DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
text_to_cstring(date_txt), "timestamp",
escontext);
goto fail;
}
}
fmask |= DTK_M(YEAR);
}
if (tmfc.ww)
{
if (tmfc.mode == FROM_CHAR_DATE_ISOWEEK)
{
/*
* If tmfc.d is not set, then the date is left at the beginning of
* the ISO week (Monday).
*/
if (tmfc.d)
isoweekdate2date(tmfc.ww, tmfc.d, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
else
isoweek2date(tmfc.ww, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
fmask |= DTK_DATE_M;
}
else
{
/* tmfc.ddd = (tmfc.ww - 1) * 7 + 1; */
if (pg_sub_s32_overflow(tmfc.ww, 1, &tmfc.ddd) ||
pg_mul_s32_overflow(tmfc.ddd, 7, &tmfc.ddd) ||
pg_add_s32_overflow(tmfc.ddd, 1, &tmfc.ddd))
{
DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
date_str, "timestamp", escontext);
goto fail;
}
}
}
if (tmfc.ddd && (tm->tm_mon <= 1 || tm->tm_mday <= 1))
{
/*
* The month and day field have not been set, so we use the
* day-of-year field to populate them. Depending on the date mode,
* this field may be interpreted as a Gregorian day-of-year, or an ISO
* week date day-of-year.
*/
if (!tm->tm_year && !tmfc.bc)
{
errsave(escontext,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("cannot calculate day of year without year information")));
goto fail;
}
if (tmfc.mode == FROM_CHAR_DATE_ISOWEEK)
{
int j0; /* zeroth day of the ISO year, in Julian */
j0 = isoweek2j(tm->tm_year, 1) - 1;
j2date(j0 + tmfc.ddd, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
fmask |= DTK_DATE_M;
}
else
{
const int *y;
int i;
/* Range-check date fields according to bit mask computed above */
if (fmask != 0)
{
/* We already dealt with AD/BC, so pass isjulian = true */
int dterr = ValidateDate(fmask, true, false, false, tm);
if (dterr != 0)
{
/*
* Force the error to be DTERR_FIELD_OVERFLOW even if ValidateDate
* said DTERR_MD_FIELD_OVERFLOW, because we don't want to print an
* irrelevant hint about datestyle.
*/
DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
date_str, "timestamp", escontext);
goto fail;
}
}
/*
* If timezone info was present, reduce it to a GMT offset. (We cannot do
* this until we've filled all of the tm struct, since the zone's offset
* might be time-varying.)
*/
if (tmfc.tzsign)
{
/* TZH and/or TZM fields */
if (tmfc.tzh < 0 || tmfc.tzh > MAX_TZDISP_HOUR ||
tmfc.tzm < 0 || tmfc.tzm >= MINS_PER_HOUR)
{
DateTimeParseError(DTERR_TZDISP_OVERFLOW, NULL,
date_str, "timestamp", escontext);
goto fail;
}
tz->has_tz = true;
tz->gmtoffset = (tmfc.tzh * MINS_PER_HOUR + tmfc.tzm) * SECS_PER_MINUTE;
/* note we are flipping the sign convention here */
if (tmfc.tzsign > 0)
tz->gmtoffset = -tz->gmtoffset;
}
else if (tmfc.has_tz)
{
/* TZ field */
tz->has_tz = true;
if (tmfc.tzp == NULL)
{
/* fixed-offset abbreviation; flip the sign convention */
tz->gmtoffset = -tmfc.gmtoffset;
}
else
{
/* dynamic-offset abbreviation, resolve using specified time */
tz->gmtoffset = DetermineTimeZoneAbbrevOffset(tm, tmfc.abbrev,
tmfc.tzp);
}
}
DEBUG_TM(tm);
if (format && !incache)
pfree(format);
pfree(date_str);
return true;
fail:
if (format && !incache)
pfree(format);
pfree(date_str);
return false;
}
/**********************************************************************
* the NUMBER version part
*********************************************************************/
/* This works the same as DCH_prevent_counter_overflow */
static inline void
NUM_prevent_counter_overflow(void)
{
if (NUMCounter >= (INT_MAX - 1))
{
for (int i = 0; i < n_NUMCache; i++)
NUMCache[i]->age >>= 1;
NUMCounter >>= 1;
}
}
/* selecta NUMCacheEntry to hold the given format picture */
static NUMCacheEntry *
NUM_cache_getnew(const char *str)
{
NUMCacheEntry *ent;
/* Ensure we can advance NUMCounter below */
NUM_prevent_counter_overflow();
/*
* If cache is full, remove oldest entry (or recycle first not-valid one)
*/
if (n_NUMCache >= NUM_CACHE_ENTRIES)
{
NUMCacheEntry *old = NUMCache[0];
#ifdef DEBUG_TO_FROM_CHAR
elog(DEBUG_elog_output, "Cache is full (%d)", n_NUMCache);
#endif
if (old->valid)
{
for (int i = 1; i < NUM_CACHE_ENTRIES; i++)
{
ent = NUMCache[i];
if (!ent->valid)
{
old = ent;
break;
}
if (ent->age < old->age)
old = ent;
}
}
#ifdef DEBUG_TO_FROM_CHAR
elog(DEBUG_elog_output, "OLD: \"%s\" AGE: %d", old->str, old->age);
#endif
old->valid = false;
strlcpy(old->str, str, NUM_CACHE_SIZE + 1);
old->age = (++NUMCounter);
/* caller is expected to fill format and Num, then set valid */
return old;
}
else
{
#ifdef DEBUG_TO_FROM_CHAR
elog(DEBUG_elog_output, "NEW (%d)", n_NUMCache);
#endif
Assert(NUMCache[n_NUMCache] == NULL);
NUMCache[n_NUMCache] = ent = (NUMCacheEntry *)
MemoryContextAllocZero(TopMemoryContext, sizeof(NUMCacheEntry));
ent->valid = false;
strlcpy(ent->str, str, NUM_CACHE_SIZE + 1);
ent->age = (++NUMCounter);
/* caller is expected to fill format and Num, then set valid */
++n_NUMCache;
return ent;
}
}
/* look for an existing NUMCacheEntry matching the given format picture */
static NUMCacheEntry *
NUM_cache_search(const char *str)
{
/* Ensure we can advance NUMCounter below */
NUM_prevent_counter_overflow();
for (int i = 0; i < n_NUMCache; i++)
{
NUMCacheEntry *ent = NUMCache[i];
/* Find or create a NUMCacheEntry for the given format picture */
static NUMCacheEntry *
NUM_cache_fetch(const char *str)
{
NUMCacheEntry *ent;
if ((ent = NUM_cache_search(str)) == NULL)
{
/*
* Not in the cache, must run parser and save a new format-picture to
* the cache. Do not mark the cache entry valid until parsing
* succeeds.
*/
ent = NUM_cache_getnew(str);
/* ----------
* Cache routine for NUM to_char version
* ----------
*/
static FormatNode *
NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree)
{
FormatNode *format = NULL;
char *str;
str = text_to_cstring(pars_str);
if (len > NUM_CACHE_SIZE)
{
/*
* Allocate new memory if format picture is bigger than static cache
* and do not use cache (call parser always)
*/
format = palloc_array(FormatNode, len + 1);
/*
* Convert integer to Roman numerals
* Result is upper-case and not blank-padded (NUM_processor converts as needed)
* If input is out-of-range, produce '###############'
*/
static char *
int_to_roman(int number)
{
int len,
num;
char *p,
*result,
numstr[12];
result = (char *) palloc(MAX_ROMAN_LEN + 1);
*result = '\0';
/*
* This range limit is the same as in Oracle(TM). The difficulty with
* handling 4000 or more is that we'd need to use more than 3 "M"'s, and
* more than 3 of the same digit isn't considered a valid Roman string.
*/
if (number > 3999 || number < 1)
{
fill_str(result, '#', MAX_ROMAN_LEN);
return result;
}
/* Convert to decimal, then examine each digit */
len = snprintf(numstr, sizeof(numstr), "%d", number);
Assert(len > 0 && len <= 4);
for (p = numstr; *p != '\0'; p++, --len)
{
num = *p - ('0' + 1);
if (num < 0)
continue; /* ignore zeroes */
/* switch on current column position */
switch (len)
{
case 4:
while (num-- >= 0)
strcat(result, "M");
break;
case 3:
strcat(result, rm100[num]);
break;
case 2:
strcat(result, rm10[num]);
break;
case 1:
strcat(result, rm1[num]);
break;
}
}
return result;
}
/*
* Convert a roman numeral (standard form) to an integer.
* Result is an integer between 1 and 3999.
* Np->inout_p is advanced past the characters consumed.
*
* If input is invalid, return -1.
*/
static int
roman_to_int(NUMProc *Np, int input_len)
{
int result = 0;
int len;
char romanChars[MAX_ROMAN_LEN];
int romanValues[MAX_ROMAN_LEN];
int repeatCount = 1;
int vCount = 0,
lCount = 0,
dCount = 0;
bool subtractionEncountered = false;
int lastSubtractedValue = 0;
/*
* Skip any leading whitespace. Perhaps we should limit the amount of
* space skipped to MAX_ROMAN_LEN, but that seems unnecessarily picky.
*/
while (!OVERLOAD_TEST && isspace((unsigned char) *Np->inout_p))
Np->inout_p++;
/*
* Collect and decode valid roman numerals, consuming at most
* MAX_ROMAN_LEN characters. We do this in a separate loop to avoid
* repeated decoding and because the main loop needs to know when it's at
* the last numeral.
*/
for (len = 0; len < MAX_ROMAN_LEN && !OVERLOAD_TEST; len++)
{
char currChar = pg_ascii_toupper(*Np->inout_p);
int currValue = ROMAN_VAL(currChar);
if (currValue == 0)
break; /* Not a valid roman numeral. */
romanChars[len] = currChar;
romanValues[len] = currValue;
Np->inout_p++;
}
if (len == 0)
return -1; /* No valid roman numerals. */
/* Check for valid combinations and compute the represented value. */
for (int i = 0; i < len; i++)
{
char currChar = romanChars[i];
int currValue = romanValues[i];
/*
* Ensure no numeral greater than or equal to the subtracted numeral
* appears after a subtraction.
*/
if (subtractionEncountered && currValue >= lastSubtractedValue)
return -1;
/*
* V, L, and D should not appear before a larger numeral, nor should
* they be repeated.
*/
if ((vCount && currValue >= ROMAN_VAL('V')) ||
(lCount && currValue >= ROMAN_VAL('L')) ||
(dCount && currValue >= ROMAN_VAL('D')))
return -1;
if (currChar == 'V')
vCount++;
else if (currChar == 'L')
lCount++;
else if (currChar == 'D')
dCount++;
if (i < len - 1)
{
/* Compare current numeral to next numeral. */
char nextChar = romanChars[i + 1];
int nextValue = romanValues[i + 1];
/*
* If the current value is less than the next value, handle
* subtraction. Verify valid subtractive combinations and update
* the result accordingly.
*/
if (currValue < nextValue)
{
if (!IS_VALID_SUB_COMB(currChar, nextChar))
return -1;
/*
* Reject cases where same numeral is repeated with
* subtraction (e.g. 'MCCM' or 'DCCCD').
*/
if (repeatCount > 1)
return -1;
/*
* We are going to skip nextChar, so first make checks needed
* for V, L, and D. These are the same as we'd have applied
* if we reached nextChar without a subtraction.
*/
if ((vCount && nextValue >= ROMAN_VAL('V')) ||
(lCount && nextValue >= ROMAN_VAL('L')) ||
(dCount && nextValue >= ROMAN_VAL('D')))
return -1;
if (nextChar == 'V')
vCount++;
else if (nextChar == 'L')
lCount++;
else if (nextChar == 'D')
dCount++;
/*
* Skip the next numeral as it is part of the subtractive
* combination.
*/ i++;
/* Update state. */
repeatCount = 1;
subtractionEncountered = true;
lastSubtractedValue = currValue;
result += (nextValue - currValue);
}
else
{
/* For same numerals, check for repetition. */
if (currChar == nextChar)
{
repeatCount++;
if (repeatCount > 3)
return -1;
}
else
repeatCount = 1;
result += currValue;
}
}
else
{
/* This is the last numeral; just add it to the result. */
result += currValue;
}
}
/*
* Number decimal point
*/
if (lconv->decimal_point && *lconv->decimal_point)
Np->decimal = lconv->decimal_point;
else
Np->decimal = ".";
if (!IS_LDECIMAL(Np->Num))
Np->decimal = ".";
/*
* Number thousands separator
*
* Some locales (e.g. broken glibc pt_BR), have a comma for decimal,
* but "" for thousands_sep, so we set the thousands_sep too.
* http://archives.postgresql.org/pgsql-hackers/2007-11/msg00772.php
*/
if (lconv->thousands_sep && *lconv->thousands_sep)
Np->L_thousands_sep = lconv->thousands_sep;
/* Make sure thousands separator doesn't match decimal point symbol. */
else if (strcmp(Np->decimal, ",") != 0)
Np->L_thousands_sep = ",";
else
Np->L_thousands_sep = ".";
/* ----------
* Return pointer of last relevant number after decimal point
* 12.0500 --> last relevant is '5'
* 12.0000 --> last relevant is '.'
* If there is no decimal point, return NULL (which will result in same
* behavior as if FM hadn't been specified).
* ----------
*/
static char *
get_last_relevant_decnum(char *num)
{
char *result,
*p = strchr(num, '.');
#ifdef DEBUG_TO_FROM_CHAR
elog(DEBUG_elog_output, "Scan for numbers (%c), current number: '%s'", *Np->inout_p, Np->number);
#endif
/*
* read digit or decimal point
*/
if (isdigit((unsigned char) *Np->inout_p))
{
if (Np->read_dec && Np->read_post == Np->Num->post)
return;
*Np->number_p = *Np->inout_p;
Np->number_p++;
if (Np->read_dec)
Np->read_post++;
else
Np->read_pre++;
isread = true;
#ifdef DEBUG_TO_FROM_CHAR
elog(DEBUG_elog_output, "Read digit (%c)", *Np->inout_p);
#endif
}
else if (IS_DECIMAL(Np->Num) && Np->read_dec == false)
{
/*
* We need not test IS_LDECIMAL(Np->Num) explicitly here, because
* Np->decimal is always just "." if we don't have a D format token.
* So we just unconditionally match to Np->decimal.
*/
int x = strlen(Np->decimal);
/*
* try read non-locale sign, which happens only if format is not exact
* and we cannot determine sign position of MI/PL/SG, an example:
*
* FM9.999999MI -> 5.01-
*
* if (.... && IS_LSIGN(Np->Num)==false) prevents read wrong formats
* like to_number('1 -', '9S') where sign is not anchored to last
* number.
*/
else if (isread == false && IS_LSIGN(Np->Num) == false &&
(IS_PLUS(Np->Num) || IS_MINUS(Np->Num)))
{
#ifdef DEBUG_TO_FROM_CHAR
elog(DEBUG_elog_output, "Try read simple post-sign (%c)", *Np->inout_p);
#endif
/* ----------
* Add digit or sign to number-string
* ----------
*/
static void
NUM_numpart_to_char(NUMProc *Np, int id)
{
int end;
if (IS_ROMAN(Np->Num))
return;
/* Note: in this elog() output not set '\0' in 'inout' */
#ifdef DEBUG_TO_FROM_CHAR
/*
* Np->num_curr is number of current item in format-picture, it is not
* current position in inout!
*/
elog(DEBUG_elog_output, "SIGN_WROTE: %d, CURRENT: %d, NUMBER_P: \"%s\", INOUT: \"%s\"",
Np->sign_wrote,
Np->num_curr,
Np->number_p,
Np->inout);
#endif
Np->num_in = false;
/*
* Append locale-specific symbol to Np->inout.
* Note we don't null-terminate the output
*/
static void
NUM_add_locale_symbol(NUMProc *Np, const char *pattern)
{
size_t pattern_len = strlen(pattern);
/* Truncate symbol if it's potentially too long */
if (unlikely(pattern_len > NUM_MAX_ITEM_SIZ))
pattern_len = pg_mbcliplen(pattern, pattern_len,
NUM_MAX_ITEM_SIZ);
memcpy(Np->inout_p, pattern, pattern_len);
Np->inout_p += pattern_len;
}
/*
* Skip over "n"input characters, but only if they aren't numeric data
*/
static void
NUM_eat_non_data_chars(NUMProc *Np, int n, int input_len)
{
const char *end = Np->inout + input_len;
while (n-- > 0)
{
if (OVERLOAD_TEST)
break; /* end of input */
if (strchr("0123456789.,+-", *Np->inout_p) != NULL)
break; /* it's a data character */
Np->inout_p += pg_mblen_range(Np->inout_p, end);
}
}
static char *
NUM_processor(FormatNode *node, NUMDesc *Num, char *inout,
char *number, int input_len, int to_char_out_pre_spaces,
int sign, bool is_to_char, Oid collid)
{
FormatNode *n;
NUMProc _Np,
*Np = &_Np;
const char *pattern;
int pattern_len;
if (IS_EEEE(Np->Num))
{
if (!Np->is_to_char)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("\"EEEE\" not supported for input")));
return strcpy(inout, number);
}
/*
* Sign
*/
if (is_to_char)
{
Np->sign = sign;
/* MI/PL/SG - write sign itself and not in number */
if (IS_PLUS(Np->Num) || IS_MINUS(Np->Num))
{
if (IS_PLUS(Np->Num) && IS_MINUS(Np->Num) == false)
Np->sign_wrote = false; /* need sign */
else
Np->sign_wrote = true; /* needn't sign */
}
else
{
if (Np->sign != '-')
{
if (IS_FILLMODE(Np->Num))
Np->Num->flag &= ~NUM_F_BRACKET;
}
if (is_to_char)
{
Np->out_pre_spaces = to_char_out_pre_spaces;
if (IS_FILLMODE(Np->Num) && IS_DECIMAL(Np->Num))
{
Np->last_relevant = get_last_relevant_decnum(Np->number);
/*
* If any '0' specifiers are present, make sure we don't strip
* those digits. But don't advance last_relevant beyond the last
* character of the Np->number string, which is a hazard if the
* number got shortened due to precision limitations.
*/
if (Np->last_relevant && Np->Num->zero_end > Np->out_pre_spaces)
{
int last_zero_pos;
char *last_zero;
/* note that Np->number cannot be zero-length here */
last_zero_pos = strlen(Np->number) - 1;
last_zero_pos = Min(last_zero_pos,
Np->Num->zero_end - Np->out_pre_spaces);
last_zero = Np->number + last_zero_pos;
if (Np->last_relevant < last_zero)
Np->last_relevant = last_zero;
}
}
/*
* Processor direct cycle
*/
if (Np->is_to_char)
Np->number_p = Np->number;
else
Np->number_p = Np->number + 1; /* first char is space for sign */
for (n = node, Np->inout_p = Np->inout; n->type != NODE_TYPE_END; n++)
{
if (!Np->is_to_char)
{
/*
* Check at least one byte remains to be scanned. (In actions
* below, must use AMOUNT_TEST if we want to read more bytes than
* that.)
*/
if (OVERLOAD_TEST)
break;
}
/*
* Format pictures actions
*/
if (n->type == NODE_TYPE_ACTION)
{
/*
* Create/read digit/zero/blank/sign/special-case
*
* 'NUM_S' note: The locale sign is anchored to number and we
* read/write it when we work with first or last number
* (NUM_0/NUM_9). This is why NUM_S is missing in switch().
*
* Notice the "Np->inout_p++" at the bottom of the loop. This is
* why most of the actions advance inout_p one less than you might
* expect. In cases where we don't want that increment to happen,
* a switch case ends with "continue" not "break".
*/
switch (n->key->id)
{
case NUM_9:
case NUM_0:
case NUM_DEC:
case NUM_D:
if (Np->is_to_char)
{
NUM_numpart_to_char(Np, n->key->id);
continue; /* for() */
}
else
{
NUM_numpart_from_char(Np, n->key->id, input_len);
break; /* switch() case: */
}
case NUM_COMMA:
if (Np->is_to_char)
{
if (!Np->num_in)
{
if (IS_FILLMODE(Np->Num))
continue;
else
*Np->inout_p = ' ';
}
else
*Np->inout_p = ',';
}
else
{
if (!Np->num_in)
{
if (IS_FILLMODE(Np->Num))
continue;
}
if (*Np->inout_p != ',')
continue;
}
break;
case NUM_G:
pattern = Np->L_thousands_sep;
pattern_len = strlen(pattern);
if (Np->is_to_char)
{
/* Truncate symbol if it's potentially too long */
if (unlikely(pattern_len > NUM_MAX_ITEM_SIZ))
pattern_len = pg_mbcliplen(pattern, pattern_len,
NUM_MAX_ITEM_SIZ);
if (!Np->num_in)
{
if (IS_FILLMODE(Np->Num))
continue;
else
{
/* just in case there are MB chars */
pattern_len = pg_mbstrlen_with_len(pattern,
pattern_len);
memset(Np->inout_p, ' ', pattern_len);
Np->inout_p += pattern_len - 1;
}
}
else
{
memcpy(Np->inout_p, pattern, pattern_len);
Np->inout_p += pattern_len - 1;
}
}
else
{
/* Here we do not truncate the symbol ... */
if (!Np->num_in)
{
if (IS_FILLMODE(Np->Num))
continue;
}
/*
* Because L_thousands_sep typically contains data
* characters (either '.' or ','), we can't use
* NUM_eat_non_data_chars here. Instead skip only if
* the input matches L_thousands_sep.
*/
if (AMOUNT_TEST(pattern_len) &&
strncmp(Np->inout_p, pattern, pattern_len) == 0)
Np->inout_p += pattern_len - 1;
else
continue;
}
break;
case NUM_L:
pattern = Np->L_currency_symbol;
if (Np->is_to_char)
{
/* Truncate symbol if it's potentially too long */
pattern_len = strlen(pattern);
if (unlikely(pattern_len > NUM_MAX_ITEM_SIZ))
pattern_len = pg_mbcliplen(pattern, pattern_len,
NUM_MAX_ITEM_SIZ);
memcpy(Np->inout_p, pattern, pattern_len);
Np->inout_p += pattern_len - 1;
}
else
{
/* Here we do not truncate the symbol ... */
NUM_eat_non_data_chars(Np, pg_mbstrlen(pattern), input_len);
continue;
}
break;
case NUM_RN:
case NUM_rn:
if (Np->is_to_char)
{
const char *number_p;
if (n->key->id == NUM_rn)
number_p = asc_tolower_z(Np->number_p);
else
number_p = Np->number_p;
if (IS_FILLMODE(Np->Num))
strcpy(Np->inout_p, number_p);
else
sprintf(Np->inout_p, "%15s", number_p);
Np->inout_p += strlen(Np->inout_p) - 1;
}
else
{
int roman_result = roman_to_int(Np, input_len);
int numlen;
if (roman_result < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid Roman numeral")));
numlen = sprintf(Np->number_p, "%d", roman_result);
Np->number_p += numlen;
Np->Num->pre = numlen;
Np->Num->post = 0;
continue; /* roman_to_int ate all the chars */
}
break;
case NUM_th:
if (IS_ROMAN(Np->Num) || *Np->number == '#' ||
Np->sign == '-' || IS_DECIMAL(Np->Num))
continue;
if (Np->is_to_char)
{
strcpy(Np->inout_p, get_th(Np->number, TH_LOWER));
Np->inout_p += 1;
}
else
{
/* All variants of 'th' occupy 2 characters */
NUM_eat_non_data_chars(Np, 2, input_len);
continue;
}
break;
case NUM_TH:
if (IS_ROMAN(Np->Num) || *Np->number == '#' ||
Np->sign == '-' || IS_DECIMAL(Np->Num))
continue;
if (Np->is_to_char)
{
strcpy(Np->inout_p, get_th(Np->number, TH_UPPER));
Np->inout_p += 1;
}
else
{
/* All variants of 'TH' occupy 2 characters */
NUM_eat_non_data_chars(Np, 2, input_len);
continue;
}
break;
case NUM_MI:
if (Np->is_to_char)
{
if (Np->sign == '-')
*Np->inout_p = '-';
else if (IS_FILLMODE(Np->Num))
continue;
else
*Np->inout_p = ' ';
}
else
{
if (*Np->inout_p == '-')
*Np->number = '-';
else
{
NUM_eat_non_data_chars(Np, 1, input_len);
continue;
}
}
break;
case NUM_PL:
if (Np->is_to_char)
{
if (Np->sign == '+')
*Np->inout_p = '+';
else if (IS_FILLMODE(Np->Num))
continue;
else
*Np->inout_p = ' ';
}
else
{
if (*Np->inout_p == '+')
*Np->number = '+';
else
{
NUM_eat_non_data_chars(Np, 1, input_len);
continue;
}
}
break;
case NUM_SG:
if (Np->is_to_char)
*Np->inout_p = Np->sign;
else
{
if (*Np->inout_p == '-')
*Np->number = '-';
else if (*Np->inout_p == '+')
*Np->number = '+';
else
{
NUM_eat_non_data_chars(Np, 1, input_len);
continue;
}
}
break;
default:
continue;
break;
}
}
else
{
/*
* In TO_CHAR, non-pattern characters in the format are copied to
* the output. In TO_NUMBER, we skip one input character for each
* non-pattern format character, whether or not it matches the
* format character.
*/
if (Np->is_to_char)
{
strcpy(Np->inout_p, n->character);
Np->inout_p += strlen(Np->inout_p);
}
else
{
Np->inout_p += pg_mblen_range(Np->inout_p, Np->inout + input_len);
}
continue;
}
Np->inout_p++;
}
/* ----------
* MACRO: Start part of NUM - for all NUM's to_char variants
* (sorry, but I hate copy same code - macro is better..)
* ----------
*/
#define NUM_TOCHAR_prepare \
do { \
int len = VARSIZE_ANY_EXHDR(fmt); \
if (len <= 0 || len >= (INT_MAX-VARHDRSZ)/NUM_MAX_ITEM_SIZ) \
PG_RETURN_TEXT_P(cstring_to_text("")); \
result = (text *) palloc0((len * NUM_MAX_ITEM_SIZ) + 1 + VARHDRSZ); \
format = NUM_cache(len, &Num, fmt, &shouldFree); \
} while (0)
/* ----------
* MACRO: Finish part of NUM
* ----------
*/
#define NUM_TOCHAR_finish \
do { \
int len; \
\
NUM_processor(format, &Num, VARDATA(result), numstr, 0, out_pre_spaces, sign, true, PG_GET_COLLATION()); \
\
if (shouldFree) \
pfree(format); \
\
/* \
* Convert null-terminated representation of result to standard text. \
* The result is usually much bigger than it needs to be, but there \
* seems little point in realloc'ing it smaller. \
*/ \
len = strlen(VARDATA(result)); \
SET_VARSIZE(result, len + VARHDRSZ); \
} while (0)
/* -------------------
* NUMERIC to_number() (convert string to numeric)
* -------------------
*/
Datum
numeric_to_number(PG_FUNCTION_ARGS)
{
text *value = PG_GETARG_TEXT_PP(0);
text *fmt = PG_GETARG_TEXT_PP(1);
NUMDesc Num;
Datum result;
FormatNode *format;
char *numstr;
bool shouldFree;
int len = 0;
int scale,
precision;
len = VARSIZE_ANY_EXHDR(fmt);
if (len <= 0 || len >= INT_MAX / NUM_MAX_ITEM_SIZ)
PG_RETURN_NULL();
if (IS_MULTI(&Num))
{
Numeric x;
Numeric a = int64_to_numeric(10);
Numeric b = int64_to_numeric(-Num.multi);
x = DatumGetNumeric(DirectFunctionCall2(numeric_power,
NumericGetDatum(a),
NumericGetDatum(b)));
result = DirectFunctionCall2(numeric_mul,
result,
NumericGetDatum(x));
}
pfree(numstr);
return result;
}
/* ------------------
* NUMERIC to_char()
* ------------------
*/
Datum
numeric_to_char(PG_FUNCTION_ARGS)
{
Numeric value = PG_GETARG_NUMERIC(0);
text *fmt = PG_GETARG_TEXT_PP(1);
NUMDesc Num;
FormatNode *format;
text *result;
bool shouldFree;
int out_pre_spaces = 0,
sign = 0;
char *numstr,
*orgnum,
*p;
NUM_TOCHAR_prepare;
/*
* On DateType depend part (numeric)
*/
if (IS_ROMAN(&Num))
{
int32 intvalue;
bool err;
/* Round and convert to int */
intvalue = numeric_int4_opt_error(value, &err);
/* On overflow, just use PG_INT32_MAX; int_to_roman will cope */
if (err)
intvalue = PG_INT32_MAX;
numstr = int_to_roman(intvalue);
}
else if (IS_EEEE(&Num))
{
orgnum = numeric_out_sci(value, Num.post);
/*
* numeric_out_sci() does not emit a sign for positive numbers. We
* need to add a space in this case so that positive and negative
* numbers are aligned. Also must check for NaN/infinity cases, which
* we handle the same way as in float8_to_char.
*/
if (strcmp(orgnum, "NaN") == 0 ||
strcmp(orgnum, "Infinity") == 0 ||
strcmp(orgnum, "-Infinity") == 0)
{
/*
* Allow 6 characters for the leading sign, the decimal point,
* "e", the exponent's sign and two exponent digits.
*/
numstr = (char *) palloc(Num.pre + Num.post + 7);
fill_str(numstr, '#', Num.pre + Num.post + 6);
*numstr = ' ';
*(numstr + Num.pre + 1) = '.';
}
else if (*orgnum != '-')
{
numstr = (char *) palloc(strlen(orgnum) + 2);
*numstr = ' ';
strcpy(numstr + 1, orgnum);
}
else
{
numstr = orgnum;
}
}
else
{
int numstr_pre_len;
Numeric val = value;
Numeric x;
if (IS_MULTI(&Num))
{
Numeric a = int64_to_numeric(10);
Numeric b = int64_to_numeric(Num.multi);
x = DatumGetNumeric(DirectFunctionCall2(numeric_power,
NumericGetDatum(a),
NumericGetDatum(b)));
val = DatumGetNumeric(DirectFunctionCall2(numeric_mul,
NumericGetDatum(value),
NumericGetDatum(x)));
Num.pre += Num.multi;
}
x = DatumGetNumeric(DirectFunctionCall2(numeric_round,
NumericGetDatum(val),
Int32GetDatum(Num.post)));
orgnum = DatumGetCString(DirectFunctionCall1(numeric_out,
NumericGetDatum(x)));
/* ---------------
* INT4 to_char()
* ---------------
*/
Datum
int4_to_char(PG_FUNCTION_ARGS)
{
int32 value = PG_GETARG_INT32(0);
text *fmt = PG_GETARG_TEXT_PP(1);
NUMDesc Num;
FormatNode *format;
text *result;
bool shouldFree;
int out_pre_spaces = 0,
sign = 0;
char *numstr,
*orgnum;
NUM_TOCHAR_prepare;
/*
* On DateType depend part (int32)
*/
if (IS_ROMAN(&Num))
numstr = int_to_roman(value);
else if (IS_EEEE(&Num))
{
/* we can do it easily because float8 won't lose any precision */
float8 val = (float8) value;
/* ---------------
* INT8 to_char()
* ---------------
*/
Datum
int8_to_char(PG_FUNCTION_ARGS)
{
int64 value = PG_GETARG_INT64(0);
text *fmt = PG_GETARG_TEXT_PP(1);
NUMDesc Num;
FormatNode *format;
text *result;
bool shouldFree;
int out_pre_spaces = 0,
sign = 0;
char *numstr,
*orgnum;
NUM_TOCHAR_prepare;
/*
* On DateType depend part (int64)
*/
if (IS_ROMAN(&Num))
{
int32 intvalue;
/* On overflow, just use PG_INT32_MAX; int_to_roman will cope */
if (value <= PG_INT32_MAX && value >= PG_INT32_MIN)
intvalue = (int32) value;
else
intvalue = PG_INT32_MAX;
numstr = int_to_roman(intvalue);
}
else if (IS_EEEE(&Num))
{
/* to avoid loss of precision, must go via numeric not float8 */
orgnum = numeric_out_sci(int64_to_numeric(value),
Num.post);
/*
* numeric_out_sci() does not emit a sign for positive numbers. We
* need to add a space in this case so that positive and negative
* numbers are aligned. We don't have to worry about NaN/inf here.
*/
if (*orgnum != '-')
{
numstr = (char *) palloc(strlen(orgnum) + 2);
*numstr = ' ';
strcpy(numstr + 1, orgnum);
}
else
{
numstr = orgnum;
}
}
else
{
int numstr_pre_len;
if (IS_MULTI(&Num))
{
double multi = pow((double) 10, (double) Num.multi);
value = DatumGetInt64(DirectFunctionCall2(int8mul,
Int64GetDatum(value),
DirectFunctionCall1(dtoi8,
Float8GetDatum(multi))));
Num.pre += Num.multi;
}
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.