aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/adt/formatting.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/utils/adt/formatting.c')
-rw-r--r--src/backend/utils/adt/formatting.c94
1 files changed, 67 insertions, 27 deletions
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index bbd97dc84bb..d2d23d31fff 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -3553,9 +3553,6 @@ to_date(PG_FUNCTION_ARGS)
*
* The TmFromChar is then analysed and converted into the final results in
* struct 'tm' and 'fsec'.
- *
- * This function does very little error checking, e.g.
- * to_timestamp('20096040','YYYYMMDD') works
*/
static void
do_to_timestamp(text *date_txt, text *fmt,
@@ -3564,30 +3561,35 @@ do_to_timestamp(text *date_txt, text *fmt,
FormatNode *format;
TmFromChar tmfc;
int fmt_len;
+ char *date_str;
+ int fmask;
+
+ date_str = text_to_cstring(date_txt);
ZERO_tmfc(&tmfc);
ZERO_tm(tm);
*fsec = 0;
+ fmask = 0; /* bit mask for ValidateDate() */
fmt_len = VARSIZE_ANY_EXHDR(fmt);
if (fmt_len)
{
char *fmt_str;
- char *date_str;
bool incache;
fmt_str = text_to_cstring(fmt);
- /*
- * Allocate new memory if format picture is bigger than static cache
- * and not use cache (call parser always)
- */
if (fmt_len > DCH_CACHE_SIZE)
{
- format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode));
+ /*
+ * Allocate new memory if format picture is bigger than static
+ * cache and not use cache (call parser always)
+ */
incache = FALSE;
+ format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode));
+
parse_format(format, fmt_str, DCH_keywords,
DCH_suff, DCH_index, DCH_TYPE, NULL);
@@ -3604,33 +3606,27 @@ do_to_timestamp(text *date_txt, text *fmt,
if ((ent = DCH_cache_search(fmt_str)) == NULL)
{
- ent = DCH_cache_getnew(fmt_str);
-
/*
* Not in the cache, must run parser and save a new
* format-picture to the cache.
*/
+ ent = DCH_cache_getnew(fmt_str);
+
parse_format(ent->format, fmt_str, DCH_keywords,
DCH_suff, DCH_index, DCH_TYPE, NULL);
(ent->format + fmt_len)->type = NODE_TYPE_END; /* Paranoia? */
-#ifdef DEBUG_TO_FROM_CHAR
- /* dump_node(ent->format, fmt_len); */
- /* dump_index(DCH_keywords, DCH_index); */
-#endif
}
format = ent->format;
}
#ifdef DEBUG_TO_FROM_CHAR
/* dump_node(format, fmt_len); */
+ /* dump_index(DCH_keywords, DCH_index); */
#endif
- date_str = text_to_cstring(date_txt);
-
DCH_from_char(format, date_str, &tmfc);
- pfree(date_str);
pfree(fmt_str);
if (!incache)
pfree(format);
@@ -3639,8 +3635,7 @@ do_to_timestamp(text *date_txt, text *fmt,
DEBUG_TMFC(&tmfc);
/*
- * Convert values that user define for FROM_CHAR (to_date/to_timestamp) to
- * standard 'tm'
+ * Convert to_date/to_timestamp input fields to standard 'tm'
*/
if (tmfc.ssss)
{
@@ -3696,19 +3691,23 @@ do_to_timestamp(text *date_txt, text *fmt,
tm->tm_year = (tmfc.cc + 1) * 100 - tm->tm_year + 1;
}
else
+ {
/* find century year for dates ending in "00" */
tm->tm_year = tmfc.cc * 100 + ((tmfc.cc >= 0) ? 0 : 1);
+ }
}
else
- /* If a 4-digit year is provided, we use that and ignore CC. */
{
+ /* If a 4-digit year is provided, we use that and ignore CC. */
tm->tm_year = tmfc.year;
if (tmfc.bc && tm->tm_year > 0)
tm->tm_year = -(tm->tm_year - 1);
}
+ fmask |= DTK_M(YEAR);
}
- else if (tmfc.cc) /* use first year of century */
+ else if (tmfc.cc)
{
+ /* use first year of century */
if (tmfc.bc)
tmfc.cc = -tmfc.cc;
if (tmfc.cc >= 0)
@@ -3717,10 +3716,14 @@ do_to_timestamp(text *date_txt, text *fmt,
else
/* +1 because year == 599 is 600 BC */
tm->tm_year = tmfc.cc * 100 + 1;
+ fmask |= DTK_M(YEAR);
}
if (tmfc.j)
+ {
j2date(tmfc.j, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
+ fmask |= DTK_DATE_M;
+ }
if (tmfc.ww)
{
@@ -3734,6 +3737,7 @@ do_to_timestamp(text *date_txt, text *fmt,
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;
@@ -3741,14 +3745,16 @@ do_to_timestamp(text *date_txt, text *fmt,
if (tmfc.w)
tmfc.dd = (tmfc.w - 1) * 7 + 1;
- if (tmfc.d)
- tm->tm_wday = tmfc.d - 1; /* convert to native numbering */
if (tmfc.dd)
+ {
tm->tm_mday = tmfc.dd;
- if (tmfc.ddd)
- tm->tm_yday = tmfc.ddd;
+ fmask |= DTK_M(DAY);
+ }
if (tmfc.mm)
+ {
tm->tm_mon = tmfc.mm;
+ fmask |= DTK_M(MONTH);
+ }
if (tmfc.ddd && (tm->tm_mon <= 1 || tm->tm_mday <= 1))
{
@@ -3771,6 +3777,7 @@ do_to_timestamp(text *date_txt, text *fmt,
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
{
@@ -3785,7 +3792,7 @@ do_to_timestamp(text *date_txt, text *fmt,
for (i = 1; i <= MONTHS_PER_YEAR; i++)
{
- if (tmfc.ddd < y[i])
+ if (tmfc.ddd <= y[i])
break;
}
if (tm->tm_mon <= 1)
@@ -3793,6 +3800,8 @@ do_to_timestamp(text *date_txt, text *fmt,
if (tm->tm_mday <= 1)
tm->tm_mday = tmfc.ddd - y[i - 1];
+
+ fmask |= DTK_M(MONTH) | DTK_M(DAY);
}
}
@@ -3808,7 +3817,38 @@ do_to_timestamp(text *date_txt, text *fmt,
*fsec += (double) tmfc.us / 1000000;
#endif
+ /* 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, date_str, "timestamp");
+ }
+ }
+
+ /* Range-check time fields too */
+ if (tm->tm_hour < 0 || tm->tm_hour >= HOURS_PER_DAY ||
+ tm->tm_min < 0 || tm->tm_min >= MINS_PER_HOUR ||
+ tm->tm_sec < 0 || tm->tm_sec >= SECS_PER_MINUTE ||
+#ifdef HAVE_INT64_TIMESTAMP
+ *fsec < INT64CONST(0) || *fsec >= USECS_PER_SEC
+#else
+ *fsec < 0 || *fsec >= 1
+#endif
+ )
+ DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp");
+
DEBUG_TM(tm);
+
+ pfree(date_str);
}