aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/src/sgml/func.sgml9
-rw-r--r--src/backend/utils/adt/formatting.c969
-rw-r--r--src/test/regress/expected/horology.out157
-rw-r--r--src/test/regress/expected/timestamp.out127
-rw-r--r--src/test/regress/expected/timestamptz.out128
-rw-r--r--src/test/regress/sql/horology.sql70
-rw-r--r--src/test/regress/sql/timestamp.sql47
-rw-r--r--src/test/regress/sql/timestamptz.sql47
8 files changed, 740 insertions, 814 deletions
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index cb4e6f991b1..05f0aabddd0 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.446 2008/09/08 00:47:40 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.447 2008/09/11 17:32:33 tgl Exp $ -->
<chapter id="functions">
<title>Functions and Operators</title>
@@ -5187,7 +5187,12 @@ SELECT SUBSTRING('XY1234Z', 'Y*?([0-9]{1,3})');
</itemizedlist>
</para>
<para>
- Attempting to construct a date using a mixture of ISO week and Gregorian date fields is nonsensical, and could yield unexpected results. In the context of an ISO year, the concept of a 'month' or 'day of month' has no meaning. In the context of a Gregorian year, the ISO week has no meaning. Users should take care to keep Gregorian and ISO date specifications separate.
+ Attempting to construct a date using a mixture of ISO week and
+ Gregorian date fields is nonsensical, and will cause an error. In the
+ context of an ISO year, the concept of a <quote>month</> or <quote>day
+ of month</> has no meaning. In the context of a Gregorian year, the
+ ISO week has no meaning. Users should take care to keep Gregorian and
+ ISO date specifications separate.
</para>
</listitem>
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 3882f84c84a..55f00c17a82 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -1,7 +1,7 @@
/* -----------------------------------------------------------------------
* formatting.c
*
- * $PostgreSQL: pgsql/src/backend/utils/adt/formatting.c,v 1.146 2008/08/22 17:57:34 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/formatting.c,v 1.147 2008/09/11 17:32:34 tgl Exp $
*
*
* Portions Copyright (c) 1999-2008, PostgreSQL Global Development Group
@@ -144,6 +144,20 @@ typedef struct
type; /* prefix / postfix */
} KeySuffix;
+/* ----------
+ * FromCharDateMode
+ * ----------
+ *
+ * This value is used to nominate one of several distinct (and mutually
+ * exclusive) date conventions that a keyword can belong to.
+ */
+typedef enum
+{
+ 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;
+
typedef struct FormatNode FormatNode;
typedef struct
@@ -152,6 +166,7 @@ typedef struct
int len;
int id;
bool is_digit;
+ FromCharDateMode date_mode;
} KeyWord;
struct FormatNode
@@ -366,6 +381,7 @@ static int NUMCounter = 0;
*/
typedef struct
{
+ FromCharDateMode mode;
int hh,
am,
pm,
@@ -377,14 +393,11 @@ typedef struct
ddd,
mm,
ms,
- iyear,
year,
bc,
- iw,
ww,
w,
cc,
- q,
j,
us,
yysz; /* is it YY or YYYY ? */
@@ -398,11 +411,11 @@ typedef struct
*/
#ifdef DEBUG_TO_FROM_CHAR
#define DEBUG_TMFC(_X) \
- elog(DEBUG_elog_output, "TMFC:\nhh %d\nam %d\npm %d\nmi %d\nss %d\nssss %d\nd %d\ndd %d\nddd %d\nmm %d\nms: %d\nyear %d\nbc %d\niw %d\nww %d\nw %d\ncc %d\nq %d\nj %d\nus: %d\nyysz: %d", \
- (_X)->hh, (_X)->am, (_X)->pm, (_X)->mi, (_X)->ss, \
+ elog(DEBUG_elog_output, "TMFC:\nmode %d\nhh %d\nam %d\npm %d\nmi %d\nss %d\nssss %d\nd %d\ndd %d\nddd %d\nmm %d\nms: %d\nyear %d\nbc %d\nww %d\nw %d\ncc %d\nj %d\nus: %d\nyysz: %d", \
+ (_X)->mode, (_X)->hh, (_X)->am, (_X)->pm, (_X)->mi, (_X)->ss, \
(_X)->ssss, (_X)->d, (_X)->dd, (_X)->ddd, (_X)->mm, (_X)->ms, \
- (_X)->year, (_X)->bc, (_X)->iw, (_X)->ww, (_X)->w, \
- (_X)->cc, (_X)->q, (_X)->j, (_X)->us, (_X)->yysz);
+ (_X)->year, (_X)->bc, (_X)->ww, (_X)->w, (_X)->cc, (_X)->j, \
+ (_X)->us, (_X)->yysz);
#define DEBUG_TM(_X) \
elog(DEBUG_elog_output, "TM:\nsec %d\nyear %d\nmin %d\nwday %d\nhour %d\nyday %d\nmday %d\nnisdst %d\nmon %d\n",\
(_X)->tm_sec, (_X)->tm_year,\
@@ -673,106 +686,108 @@ typedef enum
* ----------
*/
static const KeyWord DCH_keywords[] = {
-/* name, len, id, is_digit is in Index */
- {"A.D.", 4, DCH_A_D, FALSE}, /* A */
- {"A.M.", 4, DCH_A_M, FALSE},
- {"AD", 2, DCH_AD, FALSE},
- {"AM", 2, DCH_AM, FALSE},
- {"B.C.", 4, DCH_B_C, FALSE}, /* B */
- {"BC", 2, DCH_BC, FALSE},
- {"CC", 2, DCH_CC, TRUE}, /* C */
- {"DAY", 3, DCH_DAY, FALSE}, /* D */
- {"DDD", 3, DCH_DDD, TRUE},
- {"DD", 2, DCH_DD, TRUE},
- {"DY", 2, DCH_DY, FALSE},
- {"Day", 3, DCH_Day, FALSE},
- {"Dy", 2, DCH_Dy, FALSE},
- {"D", 1, DCH_D, TRUE},
- {"FX", 2, DCH_FX, FALSE}, /* F */
- {"HH24", 4, DCH_HH24, TRUE}, /* H */
- {"HH12", 4, DCH_HH12, TRUE},
- {"HH", 2, DCH_HH, TRUE},
- {"IDDD", 4, DCH_IDDD, TRUE}, /* I */
- {"ID", 2, DCH_ID, TRUE},
- {"IW", 2, DCH_IW, TRUE},
- {"IYYY", 4, DCH_IYYY, TRUE},
- {"IYY", 3, DCH_IYY, TRUE},
- {"IY", 2, DCH_IY, TRUE},
- {"I", 1, DCH_I, TRUE},
- {"J", 1, DCH_J, TRUE}, /* J */
- {"MI", 2, DCH_MI, TRUE}, /* M */
- {"MM", 2, DCH_MM, TRUE},
- {"MONTH", 5, DCH_MONTH, FALSE},
- {"MON", 3, DCH_MON, FALSE},
- {"MS", 2, DCH_MS, TRUE},
- {"Month", 5, DCH_Month, FALSE},
- {"Mon", 3, DCH_Mon, FALSE},
- {"P.M.", 4, DCH_P_M, FALSE}, /* P */
- {"PM", 2, DCH_PM, FALSE},
- {"Q", 1, DCH_Q, TRUE}, /* Q */
- {"RM", 2, DCH_RM, FALSE}, /* R */
- {"SSSS", 4, DCH_SSSS, TRUE}, /* S */
- {"SS", 2, DCH_SS, TRUE},
- {"TZ", 2, DCH_TZ, FALSE}, /* T */
- {"US", 2, DCH_US, TRUE}, /* U */
- {"WW", 2, DCH_WW, TRUE}, /* W */
- {"W", 1, DCH_W, TRUE},
- {"Y,YYY", 5, DCH_Y_YYY, TRUE}, /* Y */
- {"YYYY", 4, DCH_YYYY, TRUE},
- {"YYY", 3, DCH_YYY, TRUE},
- {"YY", 2, DCH_YY, TRUE},
- {"Y", 1, DCH_Y, TRUE},
- {"a.d.", 4, DCH_a_d, FALSE}, /* a */
- {"a.m.", 4, DCH_a_m, FALSE},
- {"ad", 2, DCH_ad, FALSE},
- {"am", 2, DCH_am, FALSE},
- {"b.c.", 4, DCH_b_c, FALSE}, /* b */
- {"bc", 2, DCH_bc, FALSE},
- {"cc", 2, DCH_CC, TRUE}, /* c */
- {"day", 3, DCH_day, FALSE}, /* d */
- {"ddd", 3, DCH_DDD, TRUE},
- {"dd", 2, DCH_DD, TRUE},
- {"dy", 2, DCH_dy, FALSE},
- {"d", 1, DCH_D, TRUE},
- {"fx", 2, DCH_FX, FALSE}, /* f */
- {"hh24", 4, DCH_HH24, TRUE}, /* h */
- {"hh12", 4, DCH_HH12, TRUE},
- {"hh", 2, DCH_HH, TRUE},
- {"iddd", 4, DCH_IDDD, TRUE}, /* i */
- {"id", 2, DCH_ID, TRUE},
- {"iw", 2, DCH_IW, TRUE},
- {"iyyy", 4, DCH_IYYY, TRUE},
- {"iyy", 3, DCH_IYY, TRUE},
- {"iy", 2, DCH_IY, TRUE},
- {"i", 1, DCH_I, TRUE},
- {"j", 1, DCH_J, TRUE}, /* j */
- {"mi", 2, DCH_MI, TRUE}, /* m */
- {"mm", 2, DCH_MM, TRUE},
- {"month", 5, DCH_month, FALSE},
- {"mon", 3, DCH_mon, FALSE},
- {"ms", 2, DCH_MS, TRUE},
- {"p.m.", 4, DCH_p_m, FALSE}, /* p */
- {"pm", 2, DCH_pm, FALSE},
- {"q", 1, DCH_Q, TRUE}, /* q */
- {"rm", 2, DCH_rm, FALSE}, /* r */
- {"ssss", 4, DCH_SSSS, TRUE}, /* s */
- {"ss", 2, DCH_SS, TRUE},
- {"tz", 2, DCH_tz, FALSE}, /* t */
- {"us", 2, DCH_US, TRUE}, /* u */
- {"ww", 2, DCH_WW, TRUE}, /* w */
- {"w", 1, DCH_W, TRUE},
- {"y,yyy", 5, DCH_Y_YYY, TRUE}, /* y */
- {"yyyy", 4, DCH_YYYY, TRUE},
- {"yyy", 3, DCH_YYY, TRUE},
- {"yy", 2, DCH_YY, TRUE},
- {"y", 1, DCH_Y, TRUE},
+/* name, len, id, is_digit, date_mode */
+ {"A.D.", 4, DCH_A_D, FALSE, FROM_CHAR_DATE_GREGORIAN}, /* A */
+ {"A.M.", 4, DCH_A_M, FALSE, FROM_CHAR_DATE_NONE},
+ {"AD", 2, DCH_AD, FALSE, FROM_CHAR_DATE_GREGORIAN},
+ {"AM", 2, DCH_AM, FALSE, FROM_CHAR_DATE_NONE},
+ {"B.C.", 4, DCH_B_C, FALSE, FROM_CHAR_DATE_GREGORIAN}, /* B */
+ {"BC", 2, DCH_BC, FALSE, FROM_CHAR_DATE_GREGORIAN},
+ {"CC", 2, DCH_CC, TRUE, FROM_CHAR_DATE_GREGORIAN}, /* C */
+ {"DAY", 3, DCH_DAY, FALSE, FROM_CHAR_DATE_NONE}, /* D */
+ {"DDD", 3, DCH_DDD, TRUE, FROM_CHAR_DATE_GREGORIAN},
+ {"DD", 2, DCH_DD, TRUE, FROM_CHAR_DATE_GREGORIAN},
+ {"DY", 2, DCH_DY, FALSE, FROM_CHAR_DATE_NONE},
+ {"Day", 3, DCH_Day, FALSE, FROM_CHAR_DATE_NONE},
+ {"Dy", 2, DCH_Dy, FALSE, FROM_CHAR_DATE_NONE},
+ {"D", 1, DCH_D, TRUE, FROM_CHAR_DATE_GREGORIAN},
+ {"FX", 2, DCH_FX, FALSE, FROM_CHAR_DATE_NONE}, /* F */
+ {"HH24", 4, DCH_HH24, TRUE, FROM_CHAR_DATE_NONE}, /* H */
+ {"HH12", 4, DCH_HH12, TRUE, FROM_CHAR_DATE_NONE},
+ {"HH", 2, DCH_HH, TRUE, FROM_CHAR_DATE_NONE},
+ {"IDDD", 4, DCH_IDDD, TRUE, FROM_CHAR_DATE_ISOWEEK}, /* I */
+ {"ID", 2, DCH_ID, TRUE, FROM_CHAR_DATE_ISOWEEK},
+ {"IW", 2, DCH_IW, TRUE, FROM_CHAR_DATE_ISOWEEK},
+ {"IYYY", 4, DCH_IYYY, TRUE, FROM_CHAR_DATE_ISOWEEK},
+ {"IYY", 3, DCH_IYY, TRUE, FROM_CHAR_DATE_ISOWEEK},
+ {"IY", 2, DCH_IY, TRUE, FROM_CHAR_DATE_ISOWEEK},
+ {"I", 1, DCH_I, TRUE, FROM_CHAR_DATE_ISOWEEK},
+ {"J", 1, DCH_J, TRUE, FROM_CHAR_DATE_NONE}, /* J */
+ {"MI", 2, DCH_MI, TRUE, FROM_CHAR_DATE_NONE}, /* M */
+ {"MM", 2, DCH_MM, TRUE, FROM_CHAR_DATE_GREGORIAN},
+ {"MONTH", 5, DCH_MONTH, FALSE, FROM_CHAR_DATE_GREGORIAN},
+ {"MON", 3, DCH_MON, FALSE, FROM_CHAR_DATE_GREGORIAN},
+ {"MS", 2, DCH_MS, TRUE, FROM_CHAR_DATE_NONE},
+ {"Month", 5, DCH_Month, FALSE, FROM_CHAR_DATE_GREGORIAN},
+ {"Mon", 3, DCH_Mon, FALSE, FROM_CHAR_DATE_GREGORIAN},
+ {"P.M.", 4, DCH_P_M, FALSE, FROM_CHAR_DATE_NONE}, /* P */
+ {"PM", 2, DCH_PM, FALSE, FROM_CHAR_DATE_NONE},
+ {"Q", 1, DCH_Q, TRUE, FROM_CHAR_DATE_NONE}, /* Q */
+ {"RM", 2, DCH_RM, FALSE, FROM_CHAR_DATE_GREGORIAN}, /* R */
+ {"SSSS", 4, DCH_SSSS, TRUE, FROM_CHAR_DATE_NONE}, /* S */
+ {"SS", 2, DCH_SS, TRUE, FROM_CHAR_DATE_NONE},
+ {"TZ", 2, DCH_TZ, FALSE, FROM_CHAR_DATE_NONE}, /* T */
+ {"US", 2, DCH_US, TRUE, FROM_CHAR_DATE_NONE}, /* U */
+ {"WW", 2, DCH_WW, TRUE, FROM_CHAR_DATE_GREGORIAN}, /* W */
+ {"W", 1, DCH_W, TRUE, FROM_CHAR_DATE_GREGORIAN},
+ {"Y,YYY", 5, DCH_Y_YYY, TRUE, FROM_CHAR_DATE_GREGORIAN},/* Y */
+ {"YYYY", 4, DCH_YYYY, TRUE, FROM_CHAR_DATE_GREGORIAN},
+ {"YYY", 3, DCH_YYY, TRUE, FROM_CHAR_DATE_GREGORIAN},
+ {"YY", 2, DCH_YY, TRUE, FROM_CHAR_DATE_GREGORIAN},
+ {"Y", 1, DCH_Y, TRUE, FROM_CHAR_DATE_GREGORIAN},
+ {"a.d.", 4, DCH_a_d, FALSE, FROM_CHAR_DATE_GREGORIAN}, /* a */
+ {"a.m.", 4, DCH_a_m, FALSE, FROM_CHAR_DATE_NONE},
+ {"ad", 2, DCH_ad, FALSE, FROM_CHAR_DATE_GREGORIAN},
+ {"am", 2, DCH_am, FALSE, FROM_CHAR_DATE_NONE},
+ {"b.c.", 4, DCH_b_c, FALSE, FROM_CHAR_DATE_GREGORIAN}, /* b */
+ {"bc", 2, DCH_bc, FALSE, FROM_CHAR_DATE_GREGORIAN},
+ {"cc", 2, DCH_CC, TRUE, FROM_CHAR_DATE_GREGORIAN}, /* c */
+ {"day", 3, DCH_day, FALSE, FROM_CHAR_DATE_NONE}, /* d */
+ {"ddd", 3, DCH_DDD, TRUE, FROM_CHAR_DATE_GREGORIAN},
+ {"dd", 2, DCH_DD, TRUE, FROM_CHAR_DATE_GREGORIAN},
+ {"dy", 2, DCH_dy, FALSE, FROM_CHAR_DATE_NONE},
+ {"d", 1, DCH_D, TRUE, FROM_CHAR_DATE_GREGORIAN},
+ {"fx", 2, DCH_FX, FALSE, FROM_CHAR_DATE_NONE}, /* f */
+ {"hh24", 4, DCH_HH24, TRUE, FROM_CHAR_DATE_NONE}, /* h */
+ {"hh12", 4, DCH_HH12, TRUE, FROM_CHAR_DATE_NONE},
+ {"hh", 2, DCH_HH, TRUE, FROM_CHAR_DATE_NONE},
+ {"iddd", 4, DCH_IDDD, TRUE, FROM_CHAR_DATE_ISOWEEK}, /* i */
+ {"id", 2, DCH_ID, TRUE, FROM_CHAR_DATE_ISOWEEK},
+ {"iw", 2, DCH_IW, TRUE, FROM_CHAR_DATE_ISOWEEK},
+ {"iyyy", 4, DCH_IYYY, TRUE, FROM_CHAR_DATE_ISOWEEK},
+ {"iyy", 3, DCH_IYY, TRUE, FROM_CHAR_DATE_ISOWEEK},
+ {"iy", 2, DCH_IY, TRUE, FROM_CHAR_DATE_ISOWEEK},
+ {"i", 1, DCH_I, TRUE, FROM_CHAR_DATE_ISOWEEK},
+ {"j", 1, DCH_J, TRUE, FROM_CHAR_DATE_NONE}, /* j */
+ {"mi", 2, DCH_MI, TRUE, FROM_CHAR_DATE_NONE}, /* m */
+ {"mm", 2, DCH_MM, TRUE, FROM_CHAR_DATE_GREGORIAN},
+ {"month", 5, DCH_month, FALSE, FROM_CHAR_DATE_GREGORIAN},
+ {"mon", 3, DCH_mon, FALSE, FROM_CHAR_DATE_GREGORIAN},
+ {"ms", 2, DCH_MS, TRUE, FROM_CHAR_DATE_NONE},
+ {"p.m.", 4, DCH_p_m, FALSE, FROM_CHAR_DATE_NONE}, /* p */
+ {"pm", 2, DCH_pm, FALSE, FROM_CHAR_DATE_NONE},
+ {"q", 1, DCH_Q, TRUE, FROM_CHAR_DATE_NONE}, /* q */
+ {"rm", 2, DCH_rm, FALSE, FROM_CHAR_DATE_GREGORIAN}, /* r */
+ {"ssss", 4, DCH_SSSS, TRUE, FROM_CHAR_DATE_NONE}, /* s */
+ {"ss", 2, DCH_SS, TRUE, FROM_CHAR_DATE_NONE},
+ {"tz", 2, DCH_tz, FALSE, FROM_CHAR_DATE_NONE}, /* t */
+ {"us", 2, DCH_US, TRUE, FROM_CHAR_DATE_NONE}, /* u */
+ {"ww", 2, DCH_WW, TRUE, FROM_CHAR_DATE_GREGORIAN}, /* w */
+ {"w", 1, DCH_W, TRUE, FROM_CHAR_DATE_GREGORIAN},
+ {"y,yyy", 5, DCH_Y_YYY, TRUE, FROM_CHAR_DATE_GREGORIAN},/* y */
+ {"yyyy", 4, DCH_YYYY, TRUE, FROM_CHAR_DATE_GREGORIAN},
+ {"yyy", 3, DCH_YYY, TRUE, FROM_CHAR_DATE_GREGORIAN},
+ {"yy", 2, DCH_YY, TRUE, FROM_CHAR_DATE_GREGORIAN},
+ {"y", 1, DCH_Y, TRUE, FROM_CHAR_DATE_GREGORIAN},
/* last */
- {NULL, 0, 0, 0}
+ {NULL, 0, 0, 0, 0}
};
/* ----------
- * KeyWords for NUMBER version (is_digit field is not needful here...)
+ * KeyWords for NUMBER version
+ *
+ * The is_digit and date_mode fields are not relevant here.
* ----------
*/
static const KeyWord NUM_keywords[] = {
@@ -925,8 +940,12 @@ static char *get_th(char *num, int type);
static char *str_numth(char *dest, char *num, int type);
static int strspace_len(char *str);
static int strdigits_len(char *str);
-
+static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode);
+static void from_char_set_int(int *dest, const int value, const FormatNode *node);
+static int from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node);
+static int from_char_parse_int(int *dest, char **src, FormatNode *node);
static int seq_search(char *name, char **array, int type, int max, int *len);
+static int from_char_seq_search(int *dest, char **src, char **array, int type, int max, FormatNode *node);
static void do_to_timestamp(text *date_txt, text *fmt,
struct pg_tm * tm, fsec_t *fsec);
static char *fill_str(char *str, int c, int max);
@@ -1081,7 +1100,7 @@ NUMDesc_prepare(NUMDesc *num, FormatNode *n)
NUM_cache_remove(last_NUMCacheEntry);
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("not unique \"S\"")));
+ errmsg("cannot use \"S\" twice")));
}
if (IS_PLUS(num) || IS_MINUS(num) || IS_BRACKET(num))
{
@@ -1603,80 +1622,10 @@ str_initcap_z(const char *buff)
/* ----------
- * Sequential search with to upper/lower conversion
+ * Skip TM / th in FROM_CHAR
* ----------
*/
-static int
-seq_search(char *name, char **array, int type, int max, int *len)
-{
- char *p,
- *n,
- **a;
- int last,
- i;
-
- *len = 0;
-
- if (!*name)
- return -1;
-
- /* set first char */
- if (type == ONE_UPPER || type == ALL_UPPER)
- *name = pg_toupper((unsigned char) *name);
- else if (type == ALL_LOWER)
- *name = pg_tolower((unsigned char) *name);
-
- for (last = 0, a = array; *a != NULL; a++)
- {
- /* comperate first chars */
- if (*name != **a)
- continue;
-
- for (i = 1, p = *a + 1, n = name + 1;; n++, p++, i++)
- {
- /* search fragment (max) only */
- if (max && i == max)
- {
- *len = i;
- return a - array;
- }
- /* full size */
- if (*p == '\0')
- {
- *len = i;
- return a - array;
- }
- /* Not found in array 'a' */
- if (*n == '\0')
- break;
-
- /*
- * Convert (but convert new chars only)
- */
- if (i > last)
- {
- if (type == ONE_UPPER || type == ALL_LOWER)
- *n = pg_tolower((unsigned char) *n);
- else if (type == ALL_UPPER)
- *n = pg_toupper((unsigned char) *n);
- last = i;
- }
-
-#ifdef DEBUG_TO_FROM_CHAR
-
- /*
- * elog(DEBUG_elog_output, "N: %c, P: %c, A: %s (%s)", *n, *p, *a,
- * name);
- */
-#endif
- if (*n != *p)
- break;
- }
- }
-
- return -1;
-}
-
+#define SKIP_THth(_suf) (S_THth(_suf) ? 2 : 0)
#ifdef DEBUG_TO_FROM_CHAR
/* -----------
@@ -1712,12 +1661,6 @@ dump_index(const KeyWord *k, const int *index)
#endif /* DEBUG */
/* ----------
- * Skip TM / th in FROM_CHAR
- * ----------
- */
-#define SKIP_THth(_suf) (S_THth(_suf) ? 2 : 0)
-
-/* ----------
* Return TRUE if next format picture is not digit value
* ----------
*/
@@ -1785,14 +1728,263 @@ strdigits_len(char *str)
(errcode(ERRCODE_INVALID_DATETIME_FORMAT), \
errmsg("invalid AM/PM string")));
-#define CHECK_SEQ_SEARCH(_l, _s) \
-do { \
- if ((_l) <= 0) { \
- ereport(ERROR, \
- (errcode(ERRCODE_INVALID_DATETIME_FORMAT), \
- errmsg("invalid value for %s", (_s)))); \
- } \
-} while (0)
+/*
+ * Set the date mode of a from-char conversion.
+ *
+ * Puke if the date mode has already been set, and the caller attempts to set
+ * it to a conflicting mode.
+ */
+static void
+from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode)
+{
+ if (mode != FROM_CHAR_DATE_NONE)
+ {
+ if (tmfc->mode == FROM_CHAR_DATE_NONE)
+ tmfc->mode = mode;
+ else if (tmfc->mode != mode)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("invalid combination of date conventions"),
+ errhint("Do not mix Gregorian and ISO week date "
+ "conventions in a formatting template.")));
+ }
+}
+
+/*
+ * Set the integer pointed to by 'dest' to the given value.
+ *
+ * Puke if the destination integer has previously been set to some other
+ * non-zero value.
+ */
+static void
+from_char_set_int(int *dest, const int value, const FormatNode *node)
+{
+ if (*dest != 0 && *dest != value)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("conflicting values for \"%s\" field in formatting string",
+ node->key->name),
+ errdetail("This value contradicts a previous setting for "
+ "the same field type.")));
+ *dest = value;
+}
+
+/*
+ * Read a single integer from the source string, into the int pointed to by
+ * 'dest'.
+ *
+ * In fixed-width mode (the node does not have the FM suffix), consume at most
+ * 'len' characters.
+ *
+ * We use strtol() to recover the integer value from the source string, in
+ * accordance with the given FormatNode.
+ *
+ * If the conversion completes successfully, src will have been advanced to
+ * point at the character immediately following the last character used in the
+ * conversion.
+ *
+ * Return the number of characters consumed.
+ *
+ * Note that from_char_parse_int() provides a more convenient wrapper where
+ * the length of the field is the same as the length of the format keyword (as
+ * with DD and MI).
+ */
+static int
+from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
+{
+ long result;
+ char *init = *src;
+
+ if (S_FM(node->suffix) || is_next_separator(node))
+ {
+ /*
+ * This node is in Fill Mode, or the next node is known to be a
+ * non-digit value, so we just slurp as many characters as we
+ * can get.
+ */
+ errno = 0;
+ result = strtol(init, src, 10);
+ }
+ else
+ {
+ /*
+ * We need to pull exactly the number of characters given in 'len' out
+ * of the string, and convert those.
+ */
+ char first[DCH_MAX_ITEM_SIZ + 1];
+ char *last;
+ int used;
+
+ Assert(len <= DCH_MAX_ITEM_SIZ);
+ strncpy(first, init, len);
+ first[len] = '\0';
+
+ if (strlen(first) < len)
+ ereport(ERROR,
+ (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, (int) strlen(first)),
+ errhint("If your source string is not fixed-width, try "
+ "using the \"FM\" modifier.")));
+
+ errno = 0;
+ result = strtol(first, &last, 10);
+ used = last - first;
+
+ if (used > 0 && used < len)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("invalid value for \"%s\" in source string",
+ 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)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("invalid value for \"%s\" in source string",
+ node->key->name),
+ errdetail("Value must be an integer.")));
+
+ if (errno == ERANGE || result < INT_MIN || result > INT_MAX)
+ ereport(ERROR,
+ (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)));
+
+ from_char_set_int(dest, (int) result, node);
+ return *src - init;
+}
+
+/*
+ * Call from_char_parse_int_len(), using the length of the format keyword as
+ * the expected length of the field.
+ *
+ * Don't call this function if the field differs in length from the format
+ * keyword (as with HH24; the keyword length is 4, but the field length is 2).
+ * In such cases, call from_char_parse_int_len() instead to specify the
+ * required length explictly.
+ */
+static int
+from_char_parse_int(int *dest, char **src, FormatNode *node)
+{
+ return from_char_parse_int_len(dest, src, node->key->len, node);
+}
+
+/* ----------
+ * Sequential search with to upper/lower conversion
+ * ----------
+ */
+static int
+seq_search(char *name, char **array, int type, int max, int *len)
+{
+ char *p,
+ *n,
+ **a;
+ int last,
+ i;
+
+ *len = 0;
+
+ if (!*name)
+ return -1;
+
+ /* set first char */
+ if (type == ONE_UPPER || type == ALL_UPPER)
+ *name = pg_toupper((unsigned char) *name);
+ else if (type == ALL_LOWER)
+ *name = pg_tolower((unsigned char) *name);
+
+ for (last = 0, a = array; *a != NULL; a++)
+ {
+ /* comperate first chars */
+ if (*name != **a)
+ continue;
+
+ for (i = 1, p = *a + 1, n = name + 1;; n++, p++, i++)
+ {
+ /* search fragment (max) only */
+ if (max && i == max)
+ {
+ *len = i;
+ return a - array;
+ }
+ /* full size */
+ if (*p == '\0')
+ {
+ *len = i;
+ return a - array;
+ }
+ /* Not found in array 'a' */
+ if (*n == '\0')
+ break;
+
+ /*
+ * Convert (but convert new chars only)
+ */
+ if (i > last)
+ {
+ if (type == ONE_UPPER || type == ALL_LOWER)
+ *n = pg_tolower((unsigned char) *n);
+ else if (type == ALL_UPPER)
+ *n = pg_toupper((unsigned char) *n);
+ last = i;
+ }
+
+#ifdef DEBUG_TO_FROM_CHAR
+
+ /*
+ * elog(DEBUG_elog_output, "N: %c, P: %c, A: %s (%s)", *n, *p, *a,
+ * name);
+ */
+#endif
+ if (*n != *p)
+ break;
+ }
+ }
+
+ return -1;
+}
+
+/*
+ * Perform a sequential search in 'array' for text matching the first 'max'
+ * characters of the source string.
+ *
+ * If a match is found, copy the array index of the match into the integer
+ * pointed to by 'dest', advance 'src' to the end of the part of the string
+ * which matched, and return the number of characters consumed.
+ *
+ * If the string doesn't match, throw an error.
+ */
+static int
+from_char_seq_search(int *dest, char **src, char **array, int type, int max,
+ FormatNode *node)
+{
+ int result;
+ int len;
+
+ result = seq_search(*src, array, type, max, &len);
+ if (len <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("invalid value for \"%s\" in source string",
+ node->key->name),
+ errdetail("The given value did not match any of the allowed "
+ "values for this field.")));
+ from_char_set_int(dest, result, node);
+ *src += len;
+ return len;
+}
/* ----------
* Process a TmToChar struct as denoted by a list of FormatNodes.
@@ -2258,9 +2450,7 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
{
FormatNode *n;
char *s;
- int len,
- x;
- int *target;
+ int len;
bool fx_mode = false;
for (n = node, s = in; n->type != NODE_TYPE_END && *s != '\0'; n++)
@@ -2277,6 +2467,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
continue;
}
+ from_char_set_mode(out, n->key->date_mode);
+
switch (n->key->id)
{
case DCH_FX:
@@ -2324,106 +2516,43 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
break;
case DCH_HH:
case DCH_HH12:
- if (S_FM(n->suffix) || is_next_separator(n))
- {
- sscanf(s, "%d", &out->hh);
- s += strdigits_len(s) + SKIP_THth(n->suffix);
- }
- else
- {
- sscanf(s, "%02d", &out->hh);
- s += strspace_len(s) + 2 + SKIP_THth(n->suffix);
- }
- break;
case DCH_HH24:
- if (S_FM(n->suffix) || is_next_separator(n))
- {
- sscanf(s, "%d", &out->hh);
- s += strdigits_len(s) + SKIP_THth(n->suffix);
- }
- else
- {
- sscanf(s, "%02d", &out->hh);
- s += strspace_len(s) + 2 + SKIP_THth(n->suffix);
- }
+ from_char_parse_int_len(&out->hh, &s, 2, n);
+ s += SKIP_THth(n->suffix);
break;
case DCH_MI:
- if (S_FM(n->suffix) || is_next_separator(n))
- {
- sscanf(s, "%d", &out->mi);
- s += strdigits_len(s) + SKIP_THth(n->suffix);
- }
- else
- {
- sscanf(s, "%02d", &out->mi);
- s += strspace_len(s) + 2 + SKIP_THth(n->suffix);
- }
+ from_char_parse_int(&out->mi, &s, n);
+ s += SKIP_THth(n->suffix);
break;
case DCH_SS:
- if (S_FM(n->suffix) || is_next_separator(n))
- {
- sscanf(s, "%d", &out->ss);
- s += strdigits_len(s) + SKIP_THth(n->suffix);
- }
- else
- {
- sscanf(s, "%02d", &out->ss);
- s += strspace_len(s) + 2 + SKIP_THth(n->suffix);
- }
+ from_char_parse_int(&out->ss, &s, n);
+ s += SKIP_THth(n->suffix);
break;
case DCH_MS: /* millisecond */
- if (is_next_separator(n))
- {
- sscanf(s, "%d", &out->ms);
- len = x = strdigits_len(s);
- }
- else
- {
- sscanf(s, "%03d", &out->ms);
- x = strdigits_len(s);
- len = x = x > 3 ? 3 : x;
- }
+ len = from_char_parse_int_len(&out->ms, &s, 3, n);
/*
* 25 is 0.25 and 250 is 0.25 too; 025 is 0.025 and not 0.25
*/
- out->ms *= x == 1 ? 100 :
- x == 2 ? 10 : 1;
+ out->ms *= len == 1 ? 100 :
+ len == 2 ? 10 : 1;
s += len + SKIP_THth(n->suffix);
break;
case DCH_US: /* microsecond */
- if (is_next_separator(n))
- {
- sscanf(s, "%d", &out->us);
- len = x = strdigits_len(s);
- }
- else
- {
- sscanf(s, "%06d", &out->us);
- x = strdigits_len(s);
- len = x = x > 6 ? 6 : x;
- }
+ len = from_char_parse_int_len(&out->us, &s, 6, n);
- out->us *= x == 1 ? 100000 :
- x == 2 ? 10000 :
- x == 3 ? 1000 :
- x == 4 ? 100 :
- x == 5 ? 10 : 1;
+ out->us *= len == 1 ? 100000 :
+ len == 2 ? 10000 :
+ len == 3 ? 1000 :
+ len == 4 ? 100 :
+ len == 5 ? 10 : 1;
s += len + SKIP_THth(n->suffix);
break;
case DCH_SSSS:
- if (S_FM(n->suffix) || is_next_separator(n))
- {
- sscanf(s, "%d", &out->ssss);
- s += strdigits_len(s) + SKIP_THth(n->suffix);
- }
- else
- {
- sscanf(s, "%05d", &out->ssss);
- s += strspace_len(s) + 5 + SKIP_THth(n->suffix);
- }
+ from_char_parse_int(&out->ssss, &s, n);
+ s += SKIP_THth(n->suffix);
break;
case DCH_tz:
case DCH_TZ:
@@ -2457,205 +2586,153 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
case DCH_MONTH:
case DCH_Month:
case DCH_month:
- out->mm = seq_search(s, months_full, ONE_UPPER, FULL_SIZ, &len) + 1;
- CHECK_SEQ_SEARCH(len, "MONTH/Month/month");
- s += len;
+ from_char_seq_search(&out->mm, &s, months_full, ONE_UPPER,
+ FULL_SIZ, n);
+ out->mm++;
break;
case DCH_MON:
case DCH_Mon:
case DCH_mon:
- out->mm = seq_search(s, months, ONE_UPPER, MAX_MON_LEN, &len) + 1;
- CHECK_SEQ_SEARCH(len, "MON/Mon/mon");
- s += len;
+ from_char_seq_search(&out->mm, &s, months, ONE_UPPER,
+ MAX_MON_LEN, n);
+ out->mm++;
break;
case DCH_MM:
- if (S_FM(n->suffix) || is_next_separator(n))
- {
- sscanf(s, "%d", &out->mm);
- s += strdigits_len(s) + SKIP_THth(n->suffix);
- }
- else
- {
- sscanf(s, "%02d", &out->mm);
- s += strspace_len(s) + 2 + SKIP_THth(n->suffix);
- }
+ from_char_parse_int(&out->mm, &s, n);
+ s += SKIP_THth(n->suffix);
break;
case DCH_DAY:
case DCH_Day:
case DCH_day:
- out->d = seq_search(s, days, ONE_UPPER, FULL_SIZ, &len);
- CHECK_SEQ_SEARCH(len, "DAY/Day/day");
- s += len;
+ from_char_seq_search(&out->d, &s, days, ONE_UPPER,
+ FULL_SIZ, n);
break;
case DCH_DY:
case DCH_Dy:
case DCH_dy:
- out->d = seq_search(s, days, ONE_UPPER, MAX_DY_LEN, &len);
- CHECK_SEQ_SEARCH(len, "DY/Dy/dy");
- s += len;
+ from_char_seq_search(&out->d, &s, days, ONE_UPPER,
+ MAX_DY_LEN, n);
break;
case DCH_DDD:
+ from_char_parse_int(&out->ddd, &s, n);
+ s += SKIP_THth(n->suffix);
+ break;
case DCH_IDDD:
- if (S_FM(n->suffix) || is_next_separator(n))
- {
- sscanf(s, "%d", &out->ddd);
- s += strdigits_len(s) + SKIP_THth(n->suffix);
- }
- else
- {
- sscanf(s, "%03d", &out->ddd);
- s += strspace_len(s) + 3 + SKIP_THth(n->suffix);
- }
+ from_char_parse_int_len(&out->ddd, &s, 3, n);
+ s += SKIP_THth(n->suffix);
break;
case DCH_DD:
- if (S_FM(n->suffix) || is_next_separator(n))
- {
- sscanf(s, "%d", &out->dd);
- s += strdigits_len(s) + SKIP_THth(n->suffix);
- }
- else
- {
- sscanf(s, "%02d", &out->dd);
- s += strspace_len(s) + 2 + SKIP_THth(n->suffix);
- }
+ from_char_parse_int(&out->dd, &s, n);
+ s += SKIP_THth(n->suffix);
break;
case DCH_D:
+ from_char_parse_int(&out->d, &s, n);
+ out->d--;
+ s += SKIP_THth(n->suffix);
+ break;
case DCH_ID:
- sscanf(s, "%1d", &out->d);
- if (n->key->id == DCH_D)
- out->d--;
- s += strspace_len(s) + 1 + SKIP_THth(n->suffix);
+ from_char_parse_int_len(&out->d, &s, 1, n);
+ s += SKIP_THth(n->suffix);
break;
case DCH_WW:
- if (S_FM(n->suffix) || is_next_separator(n))
- {
- sscanf(s, "%d", &out->ww);
- s += strdigits_len(s) + SKIP_THth(n->suffix);
- }
- else
- {
- sscanf(s, "%02d", &out->ww);
- s += strspace_len(s) + 2 + SKIP_THth(n->suffix);
- }
- break;
case DCH_IW:
- if (S_FM(n->suffix) || is_next_separator(n))
- {
- sscanf(s, "%d", &out->iw);
- s += strdigits_len(s) + SKIP_THth(n->suffix);
- }
- else
- {
- sscanf(s, "%02d", &out->iw);
- s += strspace_len(s) + 2 + SKIP_THth(n->suffix);
- }
+ from_char_parse_int(&out->ww, &s, n);
+ s += SKIP_THth(n->suffix);
break;
case DCH_Q:
/*
* We ignore Q when converting to date because it is not
* normative.
+ *
+ * We still parse the source string for an integer, but it
+ * isn't stored anywhere in 'out'.
*/
- s += strspace_len(s) + 1 + SKIP_THth(n->suffix);
+ from_char_parse_int((int *) NULL, &s, n);
+ s += SKIP_THth(n->suffix);
break;
case DCH_CC:
- if (S_FM(n->suffix) || is_next_separator(n))
- {
- sscanf(s, "%d", &out->cc);
- s += strdigits_len(s) + SKIP_THth(n->suffix);
- }
- else
- {
- sscanf(s, "%02d", &out->cc);
- s += strspace_len(s) + 2 + SKIP_THth(n->suffix);
- }
+ from_char_parse_int(&out->cc, &s, n);
+ s += SKIP_THth(n->suffix);
break;
case DCH_Y_YYY:
- sscanf(s, "%d,%03d", &x, &out->year);
- out->year += (x * 1000);
- out->yysz = 4;
- s += strdigits_len(s) + 4 + SKIP_THth(n->suffix);
- break;
- case DCH_YYYY:
- case DCH_IYYY:
- target = (n->key->id == DCH_YYYY) ? &out->year : &out->iyear;
-
- if (S_FM(n->suffix) || is_next_separator(n))
{
- sscanf(s, "%d", target);
+ int matched, years, millenia;
+
+ matched = sscanf(s, "%d,%03d", &millenia, &years);
+ if (matched != 2)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("invalid input string for \"Y,YYY\"")));
+ years += (millenia * 1000);
+ from_char_set_int(&out->year, years, n);
out->yysz = 4;
- s += strdigits_len(s) + SKIP_THth(n->suffix);
- }
- else
- {
- sscanf(s, "%04d", target);
- out->yysz = 4;
- s += strspace_len(s) + 4 + SKIP_THth(n->suffix);
+ s += strdigits_len(s) + 4 + SKIP_THth(n->suffix);
}
break;
+ case DCH_YYYY:
+ case DCH_IYYY:
+ from_char_parse_int(&out->year, &s, n);
+ out->yysz = 4;
+ s += SKIP_THth(n->suffix);
+ break;
case DCH_YYY:
case DCH_IYY:
- target = (n->key->id == DCH_YYY) ? &out->year : &out->iyear;
-
- sscanf(s, "%03d", target);
+ from_char_parse_int(&out->year, &s, n);
+ out->yysz = 3;
/*
* 3-digit year: '100' ... '999' = 1100 ... 1999 '000' ...
* '099' = 2000 ... 2099
*/
- if (*target >= 100)
- *target += 1000;
+ if (out->year >= 100)
+ out->year += 1000;
else
- *target += 2000;
- out->yysz = 3;
- s += strspace_len(s) + 3 + SKIP_THth(n->suffix);
+ out->year += 2000;
+ s += SKIP_THth(n->suffix);
break;
case DCH_YY:
case DCH_IY:
- target = (n->key->id == DCH_YY) ? &out->year : &out->iyear;
-
- sscanf(s, "%02d", target);
+ from_char_parse_int(&out->year, &s, n);
+ out->yysz = 2;
/*
* 2-digit year: '00' ... '69' = 2000 ... 2069 '70' ... '99'
* = 1970 ... 1999
*/
- if (*target < 70)
- *target += 2000;
+ if (out->year < 70)
+ out->year += 2000;
else
- *target += 1900;
- out->yysz = 2;
- s += strspace_len(s) + 2 + SKIP_THth(n->suffix);
+ out->year += 1900;
+ s += SKIP_THth(n->suffix);
break;
case DCH_Y:
case DCH_I:
- target = (n->key->id == DCH_Y) ? &out->year : &out->iyear;
-
- sscanf(s, "%1d", target);
+ from_char_parse_int(&out->year, &s, n);
+ out->yysz = 1;
/*
* 1-digit year: always +2000
*/
- *target += 2000;
- out->yysz = 1;
- s += strspace_len(s) + 1 + SKIP_THth(n->suffix);
+ out->year += 2000;
+ s += SKIP_THth(n->suffix);
break;
case DCH_RM:
- out->mm = 12 - seq_search(s, rm_months_upper, ALL_UPPER, FULL_SIZ, &len);
- CHECK_SEQ_SEARCH(len, "RM");
- s += len;
+ from_char_seq_search(&out->mm, &s, rm_months_upper,
+ ALL_UPPER, FULL_SIZ, n);
+ out->mm = 12 - out->mm;
break;
case DCH_rm:
- out->mm = 12 - seq_search(s, rm_months_lower, ALL_LOWER, FULL_SIZ, &len);
- CHECK_SEQ_SEARCH(len, "rm");
- s += len;
+ from_char_seq_search(&out->mm, &s, rm_months_lower,
+ ALL_LOWER, FULL_SIZ, n);
+ out->mm = 12 - out->mm;
break;
case DCH_W:
- sscanf(s, "%1d", &out->w);
- s += strspace_len(s) + 1 + SKIP_THth(n->suffix);
+ from_char_parse_int(&out->w, &s, n);
+ s += SKIP_THth(n->suffix);
break;
case DCH_J:
- sscanf(s, "%d", &out->j);
- s += strdigits_len(s) + SKIP_THth(n->suffix);
+ from_char_parse_int(&out->j, &s, n);
+ s += SKIP_THth(n->suffix);
break;
}
}
@@ -3002,8 +3079,7 @@ do_to_timestamp(text *date_txt, text *fmt,
{
FormatNode *format;
TmFromChar tmfc;
- int fmt_len,
- year;
+ int fmt_len;
ZERO_tmfc(&tmfc);
ZERO_tm(tm);
@@ -3093,12 +3169,6 @@ do_to_timestamp(text *date_txt, text *fmt,
tm->tm_sec = x;
}
- if (tmfc.ww)
- tmfc.ddd = (tmfc.ww - 1) * 7 + 1;
-
- if (tmfc.w)
- tmfc.dd = (tmfc.w - 1) * 7 + 1;
-
if (tmfc.ss)
tm->tm_sec = tmfc.ss;
if (tmfc.mi)
@@ -3120,33 +3190,7 @@ do_to_timestamp(text *date_txt, text *fmt,
tm->tm_hour = 0;
}
- switch (tmfc.q)
- {
- case 1:
- tm->tm_mday = 1;
- tm->tm_mon = 1;
- break;
- case 2:
- tm->tm_mday = 1;
- tm->tm_mon = 4;
- break;
- case 3:
- tm->tm_mday = 1;
- tm->tm_mon = 7;
- break;
- case 4:
- tm->tm_mday = 1;
- tm->tm_mon = 10;
- break;
- }
-
- /*
- * Only one year value is used. If iyear (the ISO year) is defined, it
- * takes precedence. Otherwise year (the Gregorian year) is used.
- */
- year = (tmfc.iyear) ? tmfc.iyear : tmfc.year;
-
- if (year)
+ if (tmfc.year)
{
/*
* If CC and YY (or Y) are provided, use YY as 2 low-order digits for
@@ -3157,14 +3201,14 @@ do_to_timestamp(text *date_txt, text *fmt,
*/
if (tmfc.cc && tmfc.yysz <= 2)
{
- tm->tm_year = year % 100;
+ tm->tm_year = tmfc.year % 100;
if (tm->tm_year)
tm->tm_year += (tmfc.cc - 1) * 100;
else
tm->tm_year = tmfc.cc * 100;
}
else
- tm->tm_year = year;
+ tm->tm_year = tmfc.year;
}
else if (tmfc.cc) /* use first year of century */
tm->tm_year = (tmfc.cc - 1) * 100 + 1;
@@ -3183,31 +3227,30 @@ do_to_timestamp(text *date_txt, text *fmt,
if (tmfc.j)
j2date(tmfc.j, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
- if (tmfc.iw)
+ if (tmfc.ww)
{
- /*
- * Since the user has employed the IW field, it is assumed that the
- * value in tmfc.d is in ISO day-of-week form (1 = Monday), as set by
- * the ID field. Mixing IW and D will yield weird results.
- *
- * tmfc.iyear must have been set (e.g., with IYYY) for this to work
- * properly (an ISO week without an ISO year is meaningless).
- *
- * If tmfc.d is not set, then the date is left at the beginning of the
- * ISO week (Monday).
- */
- if (tmfc.d)
- isoweekdate2date(tmfc.iw, tmfc.d, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
+ 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);
+ }
else
- isoweek2date(tmfc.iw, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
+ tmfc.ddd = (tmfc.ww - 1) * 7 + 1;
}
-
+ if (tmfc.w)
+ tmfc.dd = (tmfc.w - 1) * 7 + 1;
if (tmfc.d)
tm->tm_wday = tmfc.d;
if (tmfc.dd)
tm->tm_mday = tmfc.dd;
- if (tmfc.ddd && !tmfc.iyear)
+ if (tmfc.ddd)
tm->tm_yday = tmfc.ddd;
if (tmfc.mm)
tm->tm_mon = tmfc.mm;
@@ -3215,16 +3258,16 @@ do_to_timestamp(text *date_txt, text *fmt,
if (tmfc.ddd && (tm->tm_mon <= 1 || tm->tm_mday <= 1))
{
/*
- * If the iyear field is set, the value of ddd is taken to be an ISO
- * day-of-year. Otherwise, it is a Gregorian day-of-year. Either way,
- * since the month and day fields have not been set by some other
- * means, the value of ddd will be used to compute them.
+ * 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 (tmfc.iyear)
+ if (tmfc.mode == FROM_CHAR_DATE_ISOWEEK)
{
int j0; /* zeroth day of the ISO year, in Julian */
- j0 = isoweek2j(tmfc.iyear, 1) - 1;
+ j0 = isoweek2j(tmfc.year, 1) - 1;
j2date(j0 + tmfc.ddd, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
}
@@ -3235,7 +3278,7 @@ do_to_timestamp(text *date_txt, text *fmt,
int ysum[2][13] = {
{31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365, 0},
- {31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366, 0}};
+ {31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366, 0}};
if (!tm->tm_year)
ereport(ERROR,
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index 926a2cfde62..926d8f44585 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -2705,3 +2705,160 @@ SELECT '' AS seven, f1 AS european_sql FROM ABSTIME_TBL;
(7 rows)
RESET DateStyle;
+--
+-- to_timestamp()
+--
+SELECT '' AS to_timestamp_1, to_timestamp('0097/Feb/16 --> 08:14:30', 'YYYY/Mon/DD --> HH:MI:SS');
+ to_timestamp_1 | to_timestamp
+----------------+------------------------------
+ | Sat Feb 16 08:14:30 0097 PST
+(1 row)
+
+SELECT '' AS to_timestamp_2, to_timestamp('97/2/16 8:14:30', 'FMYYYY/FMMM/FMDD FMHH:FMMI:FMSS');
+ to_timestamp_2 | to_timestamp
+----------------+------------------------------
+ | Sat Feb 16 08:14:30 0097 PST
+(1 row)
+
+SELECT '' AS to_timestamp_3, to_timestamp('1985 January 12', 'YYYY FMMonth DD');
+ to_timestamp_3 | to_timestamp
+----------------+------------------------------
+ | Sat Jan 12 00:00:00 1985 PST
+(1 row)
+
+SELECT '' AS to_timestamp_4, to_timestamp('My birthday-> Year: 1976, Month: May, Day: 16',
+ '"My birthday-> Year" YYYY, "Month:" FMMonth, "Day:" DD');
+ to_timestamp_4 | to_timestamp
+----------------+------------------------------
+ | Sun May 16 00:00:00 1976 PDT
+(1 row)
+
+SELECT '' AS to_timestamp_5, to_timestamp('1,582nd VIII 21', 'Y,YYYth FMRM DD');
+ to_timestamp_5 | to_timestamp
+----------------+------------------------------
+ | Sat Aug 21 00:00:00 1582 PST
+(1 row)
+
+SELECT '' AS to_timestamp_6, to_timestamp('15 "text between quote marks" 98 54 45',
+ E'HH "\\text between quote marks\\"" YY MI SS');
+ to_timestamp_6 | to_timestamp
+----------------+------------------------------
+ | Thu Jan 01 15:54:45 1998 PST
+(1 row)
+
+SELECT '' AS to_timestamp_7, to_timestamp('05121445482000', 'MMDDHHMISSYYYY');
+ to_timestamp_7 | to_timestamp
+----------------+------------------------------
+ | Fri May 12 14:45:48 2000 PDT
+(1 row)
+
+SELECT '' AS to_timestamp_8, to_timestamp('2000January09Sunday', 'YYYYFMMonthDDFMDay');
+ to_timestamp_8 | to_timestamp
+----------------+------------------------------
+ | Sun Jan 09 00:00:00 2000 PST
+(1 row)
+
+SELECT '' AS to_timestamp_9, to_timestamp('97/Feb/16', 'YYMonDD');
+ERROR: invalid value for "Mon" in source string
+DETAIL: The given value did not match any of the allowed values for this field.
+SELECT '' AS to_timestamp_10, to_timestamp('19971116', 'YYYYMMDD');
+ to_timestamp_10 | to_timestamp
+-----------------+------------------------------
+ | Sun Nov 16 00:00:00 1997 PST
+(1 row)
+
+SELECT '' AS to_timestamp_11, to_timestamp('20000-1116', 'YYYY-MMDD');
+ to_timestamp_11 | to_timestamp
+-----------------+-------------------------------
+ | Thu Nov 16 00:00:00 20000 PST
+(1 row)
+
+SELECT '' AS to_timestamp_12, to_timestamp('9-1116', 'Y-MMDD');
+ to_timestamp_12 | to_timestamp
+-----------------+------------------------------
+ | Mon Nov 16 00:00:00 2009 PST
+(1 row)
+
+SELECT '' AS to_timestamp_13, to_timestamp('95-1116', 'YY-MMDD');
+ to_timestamp_13 | to_timestamp
+-----------------+------------------------------
+ | Thu Nov 16 00:00:00 1995 PST
+(1 row)
+
+SELECT '' AS to_timestamp_14, to_timestamp('995-1116', 'YYY-MMDD');
+ to_timestamp_14 | to_timestamp
+-----------------+------------------------------
+ | Thu Nov 16 00:00:00 1995 PST
+(1 row)
+
+SELECT '' AS to_timestamp_15, to_timestamp('2005426', 'YYYYWWD');
+ to_timestamp_15 | to_timestamp
+-----------------+------------------------------
+ | Sat Oct 15 00:00:00 2005 PDT
+(1 row)
+
+SELECT '' AS to_timestamp_16, to_timestamp('2005300', 'YYYYDDD');
+ to_timestamp_16 | to_timestamp
+-----------------+------------------------------
+ | Thu Oct 27 00:00:00 2005 PDT
+(1 row)
+
+SELECT '' AS to_timestamp_17, to_timestamp('2005527', 'IYYYIWID');
+ to_timestamp_17 | to_timestamp
+-----------------+------------------------------
+ | Sun Jan 01 00:00:00 2006 PST
+(1 row)
+
+SELECT '' AS to_timestamp_18, to_timestamp('005527', 'IYYIWID');
+ to_timestamp_18 | to_timestamp
+-----------------+------------------------------
+ | Sun Jan 01 00:00:00 2006 PST
+(1 row)
+
+SELECT '' AS to_timestamp_19, to_timestamp('05527', 'IYIWID');
+ to_timestamp_19 | to_timestamp
+-----------------+------------------------------
+ | Sun Jan 01 00:00:00 2006 PST
+(1 row)
+
+SELECT '' AS to_timestamp_20, to_timestamp('5527', 'IIWID');
+ to_timestamp_20 | to_timestamp
+-----------------+------------------------------
+ | Sun Jan 01 00:00:00 2006 PST
+(1 row)
+
+SELECT '' AS to_timestamp_21, to_timestamp('2005364', 'IYYYIDDD');
+ to_timestamp_21 | to_timestamp
+-----------------+------------------------------
+ | Sun Jan 01 00:00:00 2006 PST
+(1 row)
+
+--
+-- Check errors for some incorrect usages of to_timestamp()
+--
+-- Mixture of date conventions (ISO week and Gregorian):
+SELECT '' AS to_timestamp_22, to_timestamp('2005527', 'YYYYIWID');
+ERROR: invalid combination of date conventions
+HINT: Do not mix Gregorian and ISO week date conventions in a formatting template.
+-- Insufficient characters in the source string:
+SELECT '' AS to_timestamp_23, to_timestamp('19971', 'YYYYMMDD');
+ERROR: source string too short for "MM" formatting field
+DETAIL: Field requires 2 characters, but only 1 remain.
+HINT: If your source string is not fixed-width, try using the "FM" modifier.
+-- Insufficient digit characters for a single node:
+SELECT '' AS to_timestamp_24, to_timestamp('19971)24', 'YYYYMMDD');
+ERROR: invalid value for "MM" in source string
+DETAIL: Field requires 2 characters, but only 1 could be parsed.
+HINT: If your source string is not fixed-width, try using the "FM" modifier.
+-- Value clobbering:
+SELECT '' AS to_timestamp_25, to_timestamp('1997-11-Jan-16', 'YYYY-MM-Mon-DD');
+ERROR: conflicting values for "Mon" field in formatting string
+DETAIL: This value contradicts a previous setting for the same field type.
+-- Non-numeric input:
+SELECT '' AS to_timestamp_26, to_timestamp('199711xy', 'YYYYMMDD');
+ERROR: invalid value for "DD" in source string
+DETAIL: Value must be an integer.
+-- Input that doesn't fit in an int:
+SELECT '' AS to_timestamp_27, to_timestamp('10000000000', 'FMYYYY');
+ERROR: value for "YYYY" in source string is out of range
+DETAIL: Value must be in the range -2147483648 to 2147483647.
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index a0924c7fe3b..3f5a0677f58 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -1590,130 +1590,3 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
| 2001 001 01 1 1 1 1
(65 rows)
--- TO_TIMESTAMP()
-SELECT '' AS to_timestamp_1, to_timestamp('0097/Feb/16 --> 08:14:30', 'YYYY/Mon/DD --> HH:MI:SS');
- to_timestamp_1 | to_timestamp
-----------------+------------------------------
- | Sat Feb 16 08:14:30 0097 PST
-(1 row)
-
-SELECT '' AS to_timestamp_2, to_timestamp('97/2/16 8:14:30', 'FMYYYY/FMMM/FMDD FMHH:FMMI:FMSS');
- to_timestamp_2 | to_timestamp
-----------------+------------------------------
- | Sat Feb 16 08:14:30 0097 PST
-(1 row)
-
-SELECT '' AS to_timestamp_3, to_timestamp('1985 January 12', 'YYYY FMMonth DD');
- to_timestamp_3 | to_timestamp
-----------------+------------------------------
- | Sat Jan 12 00:00:00 1985 PST
-(1 row)
-
-SELECT '' AS to_timestamp_4, to_timestamp('My birthday-> Year: 1976, Month: May, Day: 16',
- '"My birthday-> Year" YYYY, "Month:" FMMonth, "Day:" DD');
- to_timestamp_4 | to_timestamp
-----------------+------------------------------
- | Sun May 16 00:00:00 1976 PDT
-(1 row)
-
-SELECT '' AS to_timestamp_5, to_timestamp('1,582nd VIII 21', 'Y,YYYth FMRM DD');
- to_timestamp_5 | to_timestamp
-----------------+------------------------------
- | Sat Aug 21 00:00:00 1582 PST
-(1 row)
-
-SELECT '' AS to_timestamp_6, to_timestamp('15 "text between quote marks" 98 54 45',
- E'HH "\\text between quote marks\\"" YY MI SS');
- to_timestamp_6 | to_timestamp
-----------------+------------------------------
- | Thu Jan 01 15:54:45 1998 PST
-(1 row)
-
-
-SELECT '' AS to_timestamp_7, to_timestamp('05121445482000', 'MMDDHHMISSYYYY');
- to_timestamp_7 | to_timestamp
-----------------+------------------------------
- | Fri May 12 14:45:48 2000 PDT
-(1 row)
-
-SELECT '' AS to_timestamp_8, to_timestamp('2000January09Sunday', 'YYYYFMMonthDDFMDay');
- to_timestamp_8 | to_timestamp
-----------------+------------------------------
- | Sun Jan 09 00:00:00 2000 PST
-(1 row)
-
-SELECT '' AS to_timestamp_9, to_timestamp('97/Feb/16', 'YYMonDD');
-ERROR: invalid value for MON/Mon/mon
-SELECT '' AS to_timestamp_10, to_timestamp('19971116', 'YYYYMMDD');
- to_timestamp_10 | to_timestamp
------------------+------------------------------
- | Sun Nov 16 00:00:00 1997 PST
-(1 row)
-
-SELECT '' AS to_timestamp_11, to_timestamp('20000-1116', 'YYYY-MMDD');
- to_timestamp_11 | to_timestamp
------------------+-------------------------------
- | Thu Nov 16 00:00:00 20000 PST
-(1 row)
-
-SELECT '' AS to_timestamp_12, to_timestamp('9-1116', 'Y-MMDD');
- to_timestamp_12 | to_timestamp
------------------+------------------------------
- | Mon Nov 16 00:00:00 2009 PST
-(1 row)
-
-SELECT '' AS to_timestamp_13, to_timestamp('95-1116', 'YY-MMDD');
- to_timestamp_13 | to_timestamp
------------------+------------------------------
- | Thu Nov 16 00:00:00 1995 PST
-(1 row)
-
-SELECT '' AS to_timestamp_14, to_timestamp('995-1116', 'YYY-MMDD');
- to_timestamp_14 | to_timestamp
------------------+------------------------------
- | Thu Nov 16 00:00:00 1995 PST
-(1 row)
-
-SELECT '' AS to_timestamp_15, to_timestamp('2005426', 'YYYYWWD');
- to_timestamp_15 | to_timestamp
------------------+------------------------------
- | Sat Oct 15 00:00:00 2005 PDT
-(1 row)
-
-SELECT '' AS to_timestamp_16, to_timestamp('2005300', 'YYYYDDD');
- to_timestamp_16 | to_timestamp
------------------+------------------------------
- | Thu Oct 27 00:00:00 2005 PDT
-(1 row)
-
-SELECT '' AS to_timestamp_17, to_timestamp('2005527', 'IYYYIWID');
- to_timestamp_17 | to_timestamp
------------------+------------------------------
- | Sun Jan 01 00:00:00 2006 PST
-(1 row)
-
-SELECT '' AS to_timestamp_18, to_timestamp('005527', 'IYYIWID');
- to_timestamp_18 | to_timestamp
------------------+------------------------------
- | Sun Jan 01 00:00:00 2006 PST
-(1 row)
-
-SELECT '' AS to_timestamp_19, to_timestamp('05527', 'IYIWID');
- to_timestamp_19 | to_timestamp
------------------+------------------------------
- | Sun Jan 01 00:00:00 2006 PST
-(1 row)
-
-SELECT '' AS to_timestamp_20, to_timestamp('5527', 'IIWID');
- to_timestamp_20 | to_timestamp
------------------+------------------------------
- | Sun Jan 01 00:00:00 2006 PST
-(1 row)
-
-SELECT '' AS to_timestamp_21, to_timestamp('2005364', 'IYYYIDDD');
- to_timestamp_21 | to_timestamp
------------------+------------------------------
- | Sun Jan 01 00:00:00 2006 PST
-(1 row)
-
-SET DateStyle TO DEFAULT;
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 31012f80b67..5849d8f02e0 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -1684,131 +1684,3 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
| 2001 001 01 1 1 1 1
(66 rows)
--- TO_TIMESTAMP()
-SELECT '' AS to_timestamp_1, to_timestamp('0097/Feb/16 --> 08:14:30', 'YYYY/Mon/DD --> HH:MI:SS');
- to_timestamp_1 | to_timestamp
-----------------+------------------------------
- | Sat Feb 16 08:14:30 0097 PST
-(1 row)
-
-
-SELECT '' AS to_timestamp_2, to_timestamp('97/2/16 8:14:30', 'FMYYYY/FMMM/FMDD FMHH:FMMI:FMSS');
- to_timestamp_2 | to_timestamp
-----------------+------------------------------
- | Sat Feb 16 08:14:30 0097 PST
-(1 row)
-
-SELECT '' AS to_timestamp_3, to_timestamp('1985 January 12', 'YYYY FMMonth DD');
- to_timestamp_3 | to_timestamp
-----------------+------------------------------
- | Sat Jan 12 00:00:00 1985 PST
-(1 row)
-
-SELECT '' AS to_timestamp_4, to_timestamp('My birthday-> Year: 1976, Month: May, Day: 16',
- '"My birthday-> Year" YYYY, "Month:" FMMonth, "Day:" DD');
- to_timestamp_4 | to_timestamp
-----------------+------------------------------
- | Sun May 16 00:00:00 1976 PDT
-(1 row)
-
-SELECT '' AS to_timestamp_5, to_timestamp('1,582nd VIII 21', 'Y,YYYth FMRM DD');
- to_timestamp_5 | to_timestamp
-----------------+------------------------------
- | Sat Aug 21 00:00:00 1582 PST
-(1 row)
-
-SELECT '' AS to_timestamp_6, to_timestamp('15 "text between quote marks" 98 54 45',
- E'HH "\\text between quote marks\\"" YY MI SS');
- to_timestamp_6 | to_timestamp
-----------------+------------------------------
- | Thu Jan 01 15:54:45 1998 PST
-(1 row)
-
-
-SELECT '' AS to_timestamp_7, to_timestamp('05121445482000', 'MMDDHHMISSYYYY');
- to_timestamp_7 | to_timestamp
-----------------+------------------------------
- | Fri May 12 14:45:48 2000 PDT
-(1 row)
-
-SELECT '' AS to_timestamp_8, to_timestamp('2000January09Sunday', 'YYYYFMMonthDDFMDay');
- to_timestamp_8 | to_timestamp
-----------------+------------------------------
- | Sun Jan 09 00:00:00 2000 PST
-(1 row)
-
-SELECT '' AS to_timestamp_9, to_timestamp('97/Feb/16', 'YYMonDD');
-ERROR: invalid value for MON/Mon/mon
-SELECT '' AS to_timestamp_10, to_timestamp('19971116', 'YYYYMMDD');
- to_timestamp_10 | to_timestamp
------------------+------------------------------
- | Sun Nov 16 00:00:00 1997 PST
-(1 row)
-
-SELECT '' AS to_timestamp_11, to_timestamp('20000-1116', 'YYYY-MMDD');
- to_timestamp_11 | to_timestamp
------------------+-------------------------------
- | Thu Nov 16 00:00:00 20000 PST
-(1 row)
-
-SELECT '' AS to_timestamp_12, to_timestamp('9-1116', 'Y-MMDD');
- to_timestamp_12 | to_timestamp
------------------+------------------------------
- | Mon Nov 16 00:00:00 2009 PST
-(1 row)
-
-SELECT '' AS to_timestamp_13, to_timestamp('95-1116', 'YY-MMDD');
- to_timestamp_13 | to_timestamp
------------------+------------------------------
- | Thu Nov 16 00:00:00 1995 PST
-(1 row)
-
-SELECT '' AS to_timestamp_14, to_timestamp('995-1116', 'YYY-MMDD');
- to_timestamp_14 | to_timestamp
------------------+------------------------------
- | Thu Nov 16 00:00:00 1995 PST
-(1 row)
-
-SELECT '' AS to_timestamp_15, to_timestamp('2005426', 'YYYYWWD');
- to_timestamp_15 | to_timestamp
------------------+------------------------------
- | Sat Oct 15 00:00:00 2005 PDT
-(1 row)
-
-SELECT '' AS to_timestamp_16, to_timestamp('2005300', 'YYYYDDD');
- to_timestamp_16 | to_timestamp
------------------+------------------------------
- | Thu Oct 27 00:00:00 2005 PDT
-(1 row)
-
-SELECT '' AS to_timestamp_17, to_timestamp('2005527', 'IYYYIWID');
- to_timestamp_17 | to_timestamp
------------------+------------------------------
- | Sun Jan 01 00:00:00 2006 PST
-(1 row)
-
-SELECT '' AS to_timestamp_18, to_timestamp('005527', 'IYYIWID');
- to_timestamp_18 | to_timestamp
------------------+------------------------------
- | Sun Jan 01 00:00:00 2006 PST
-(1 row)
-
-SELECT '' AS to_timestamp_19, to_timestamp('05527', 'IYIWID');
- to_timestamp_19 | to_timestamp
------------------+------------------------------
- | Sun Jan 01 00:00:00 2006 PST
-(1 row)
-
-SELECT '' AS to_timestamp_20, to_timestamp('5527', 'IIWID');
- to_timestamp_20 | to_timestamp
------------------+------------------------------
- | Sun Jan 01 00:00:00 2006 PST
-(1 row)
-
-SELECT '' AS to_timestamp_21, to_timestamp('2005364', 'IYYYIDDD');
- to_timestamp_21 | to_timestamp
------------------+------------------------------
- | Sun Jan 01 00:00:00 2006 PST
-(1 row)
-
-SET DateStyle TO DEFAULT;
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f6caffa7347..2ac9b0af1a9 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -374,3 +374,73 @@ SELECT '' AS "65", d1 AS european_sql FROM TIMESTAMP_TBL;
SELECT '' AS seven, f1 AS european_sql FROM ABSTIME_TBL;
RESET DateStyle;
+
+--
+-- to_timestamp()
+--
+
+SELECT '' AS to_timestamp_1, to_timestamp('0097/Feb/16 --> 08:14:30', 'YYYY/Mon/DD --> HH:MI:SS');
+
+SELECT '' AS to_timestamp_2, to_timestamp('97/2/16 8:14:30', 'FMYYYY/FMMM/FMDD FMHH:FMMI:FMSS');
+
+SELECT '' AS to_timestamp_3, to_timestamp('1985 January 12', 'YYYY FMMonth DD');
+
+SELECT '' AS to_timestamp_4, to_timestamp('My birthday-> Year: 1976, Month: May, Day: 16',
+ '"My birthday-> Year" YYYY, "Month:" FMMonth, "Day:" DD');
+
+SELECT '' AS to_timestamp_5, to_timestamp('1,582nd VIII 21', 'Y,YYYth FMRM DD');
+
+SELECT '' AS to_timestamp_6, to_timestamp('15 "text between quote marks" 98 54 45',
+ E'HH "\\text between quote marks\\"" YY MI SS');
+
+SELECT '' AS to_timestamp_7, to_timestamp('05121445482000', 'MMDDHHMISSYYYY');
+
+SELECT '' AS to_timestamp_8, to_timestamp('2000January09Sunday', 'YYYYFMMonthDDFMDay');
+
+SELECT '' AS to_timestamp_9, to_timestamp('97/Feb/16', 'YYMonDD');
+
+SELECT '' AS to_timestamp_10, to_timestamp('19971116', 'YYYYMMDD');
+
+SELECT '' AS to_timestamp_11, to_timestamp('20000-1116', 'YYYY-MMDD');
+
+SELECT '' AS to_timestamp_12, to_timestamp('9-1116', 'Y-MMDD');
+
+SELECT '' AS to_timestamp_13, to_timestamp('95-1116', 'YY-MMDD');
+
+SELECT '' AS to_timestamp_14, to_timestamp('995-1116', 'YYY-MMDD');
+
+SELECT '' AS to_timestamp_15, to_timestamp('2005426', 'YYYYWWD');
+
+SELECT '' AS to_timestamp_16, to_timestamp('2005300', 'YYYYDDD');
+
+SELECT '' AS to_timestamp_17, to_timestamp('2005527', 'IYYYIWID');
+
+SELECT '' AS to_timestamp_18, to_timestamp('005527', 'IYYIWID');
+
+SELECT '' AS to_timestamp_19, to_timestamp('05527', 'IYIWID');
+
+SELECT '' AS to_timestamp_20, to_timestamp('5527', 'IIWID');
+
+SELECT '' AS to_timestamp_21, to_timestamp('2005364', 'IYYYIDDD');
+
+--
+-- Check errors for some incorrect usages of to_timestamp()
+--
+
+-- Mixture of date conventions (ISO week and Gregorian):
+SELECT '' AS to_timestamp_22, to_timestamp('2005527', 'YYYYIWID');
+
+-- Insufficient characters in the source string:
+SELECT '' AS to_timestamp_23, to_timestamp('19971', 'YYYYMMDD');
+
+-- Insufficient digit characters for a single node:
+SELECT '' AS to_timestamp_24, to_timestamp('19971)24', 'YYYYMMDD');
+
+-- Value clobbering:
+SELECT '' AS to_timestamp_25, to_timestamp('1997-11-Jan-16', 'YYYY-MM-Mon-DD');
+
+-- Non-numeric input:
+SELECT '' AS to_timestamp_26, to_timestamp('199711xy', 'YYYYMMDD');
+
+-- Input that doesn't fit in an int:
+SELECT '' AS to_timestamp_27, to_timestamp('10000000000', 'FMYYYY');
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index bfbfb87feb8..8f73e9f45bd 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -223,50 +223,3 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
FROM TIMESTAMP_TBL;
-
--- TO_TIMESTAMP()
-SELECT '' AS to_timestamp_1, to_timestamp('0097/Feb/16 --> 08:14:30', 'YYYY/Mon/DD --> HH:MI:SS');
-
-SELECT '' AS to_timestamp_2, to_timestamp('97/2/16 8:14:30', 'FMYYYY/FMMM/FMDD FMHH:FMMI:FMSS');
-
-SELECT '' AS to_timestamp_3, to_timestamp('1985 January 12', 'YYYY FMMonth DD');
-
-SELECT '' AS to_timestamp_4, to_timestamp('My birthday-> Year: 1976, Month: May, Day: 16',
- '"My birthday-> Year" YYYY, "Month:" FMMonth, "Day:" DD');
-
-SELECT '' AS to_timestamp_5, to_timestamp('1,582nd VIII 21', 'Y,YYYth FMRM DD');
-
-SELECT '' AS to_timestamp_6, to_timestamp('15 "text between quote marks" 98 54 45',
- E'HH "\\text between quote marks\\"" YY MI SS');
-
-SELECT '' AS to_timestamp_7, to_timestamp('05121445482000', 'MMDDHHMISSYYYY');
-
-SELECT '' AS to_timestamp_8, to_timestamp('2000January09Sunday', 'YYYYFMMonthDDFMDay');
-
-SELECT '' AS to_timestamp_9, to_timestamp('97/Feb/16', 'YYMonDD');
-
-SELECT '' AS to_timestamp_10, to_timestamp('19971116', 'YYYYMMDD');
-
-SELECT '' AS to_timestamp_11, to_timestamp('20000-1116', 'YYYY-MMDD');
-
-SELECT '' AS to_timestamp_12, to_timestamp('9-1116', 'Y-MMDD');
-
-SELECT '' AS to_timestamp_13, to_timestamp('95-1116', 'YY-MMDD');
-
-SELECT '' AS to_timestamp_14, to_timestamp('995-1116', 'YYY-MMDD');
-
-SELECT '' AS to_timestamp_15, to_timestamp('2005426', 'YYYYWWD');
-
-SELECT '' AS to_timestamp_16, to_timestamp('2005300', 'YYYYDDD');
-
-SELECT '' AS to_timestamp_17, to_timestamp('2005527', 'IYYYIWID');
-
-SELECT '' AS to_timestamp_18, to_timestamp('005527', 'IYYIWID');
-
-SELECT '' AS to_timestamp_19, to_timestamp('05527', 'IYIWID');
-
-SELECT '' AS to_timestamp_20, to_timestamp('5527', 'IIWID');
-
-SELECT '' AS to_timestamp_21, to_timestamp('2005364', 'IYYYIDDD');
-
-SET DateStyle TO DEFAULT;
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index 07d8bd357ba..7da3f8fc92f 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -241,50 +241,3 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
FROM TIMESTAMPTZ_TBL;
-
--- TO_TIMESTAMP()
-SELECT '' AS to_timestamp_1, to_timestamp('0097/Feb/16 --> 08:14:30', 'YYYY/Mon/DD --> HH:MI:SS');
-
-SELECT '' AS to_timestamp_2, to_timestamp('97/2/16 8:14:30', 'FMYYYY/FMMM/FMDD FMHH:FMMI:FMSS');
-
-SELECT '' AS to_timestamp_3, to_timestamp('1985 January 12', 'YYYY FMMonth DD');
-
-SELECT '' AS to_timestamp_4, to_timestamp('My birthday-> Year: 1976, Month: May, Day: 16',
- '"My birthday-> Year" YYYY, "Month:" FMMonth, "Day:" DD');
-
-SELECT '' AS to_timestamp_5, to_timestamp('1,582nd VIII 21', 'Y,YYYth FMRM DD');
-
-SELECT '' AS to_timestamp_6, to_timestamp('15 "text between quote marks" 98 54 45',
- E'HH "\\text between quote marks\\"" YY MI SS');
-
-SELECT '' AS to_timestamp_7, to_timestamp('05121445482000', 'MMDDHHMISSYYYY');
-
-SELECT '' AS to_timestamp_8, to_timestamp('2000January09Sunday', 'YYYYFMMonthDDFMDay');
-
-SELECT '' AS to_timestamp_9, to_timestamp('97/Feb/16', 'YYMonDD');
-
-SELECT '' AS to_timestamp_10, to_timestamp('19971116', 'YYYYMMDD');
-
-SELECT '' AS to_timestamp_11, to_timestamp('20000-1116', 'YYYY-MMDD');
-
-SELECT '' AS to_timestamp_12, to_timestamp('9-1116', 'Y-MMDD');
-
-SELECT '' AS to_timestamp_13, to_timestamp('95-1116', 'YY-MMDD');
-
-SELECT '' AS to_timestamp_14, to_timestamp('995-1116', 'YYY-MMDD');
-
-SELECT '' AS to_timestamp_15, to_timestamp('2005426', 'YYYYWWD');
-
-SELECT '' AS to_timestamp_16, to_timestamp('2005300', 'YYYYDDD');
-
-SELECT '' AS to_timestamp_17, to_timestamp('2005527', 'IYYYIWID');
-
-SELECT '' AS to_timestamp_18, to_timestamp('005527', 'IYYIWID');
-
-SELECT '' AS to_timestamp_19, to_timestamp('05527', 'IYIWID');
-
-SELECT '' AS to_timestamp_20, to_timestamp('5527', 'IIWID');
-
-SELECT '' AS to_timestamp_21, to_timestamp('2005364', 'IYYYIDDD');
-
-SET DateStyle TO DEFAULT;