Refactor to_char/to_date formatting code; primarily, replace DCH_processor
authorTom Lane
Sat, 22 Mar 2008 22:32:19 +0000 (22:32 +0000)
committerTom Lane
Sat, 22 Mar 2008 22:32:19 +0000 (22:32 +0000)
with two new functions DCH_to_char and DCH_from_char that have less confusing
APIs.  Brendan Jurd

src/backend/utils/adt/formatting.c

index 02b2a905ef4846855411066ddbe110a70b16819e..d1cc69a73227679e08d4ed2f644ad02c2204b8d1 100644 (file)
@@ -1,7 +1,7 @@
 /* -----------------------------------------------------------------------
  * formatting.c
  *
- * $PostgreSQL: pgsql/src/backend/utils/adt/formatting.c,v 1.137 2008/01/01 19:45:52 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/formatting.c,v 1.138 2008/03/22 22:32:19 tgl Exp $
  *
  *
  *  Portions Copyright (c) 1999-2008, PostgreSQL Global Development Group
@@ -140,21 +140,18 @@ typedef struct FormatNode FormatNode;
 
 typedef struct
 {
-   const char *name;           /* keyword          */
-   int         len;            /* keyword length       */
-   int         (*action) (int arg, char *inout,        /* action for keyword */
-                                 int suf, bool is_to_char, bool is_interval,
-                                      FormatNode *node, void *data);
-   int         id;             /* keyword id           */
-   bool        isitdigit;      /* is expected output/input digit */
+   const char *name;
+   int         len;
+   int         id;
+   bool        is_digit;
 } KeyWord;
 
 struct FormatNode
 {
    int         type;           /* node type            */
    const KeyWord *key;         /* if node type is KEYWORD  */
-   int         character,      /* if node type is CHAR     */
-               suffix;         /* keyword suffix       */
+   char        character;      /* if node type is CHAR     */
+   int         suffix;         /* keyword suffix       */
 };
 
 #define NODE_TYPE_END      1
@@ -179,10 +176,8 @@ static char *days_short[] = {
 };
 
 /* ----------
- * AC / DC
+ * AD / BC
  * ----------
- */
-/*
  * There is no 0 AD.  Years go from 1 BC to 1 AD, so we make it
  * positive and map year == -1 to year zero, and shift all negative
  * years up one.  For interval years, we just return the year.
@@ -218,8 +213,8 @@ static char *days_short[] = {
 
 /* ----------
  * Months in roman-numeral
- * (Must be conversely for seq_search (in FROM_CHAR), because
- * 'VIII' must be over 'V')
+ * (Must be in reverse order for seq_search (in FROM_CHAR), because
+ * 'VIII' must have higher precedence than 'V')
  * ----------
  */
 static char *rm_months_upper[] =
@@ -259,13 +254,6 @@ static char *numth[] = {"st", "nd", "rd", "th", NULL};
 #define TH_UPPER   1
 #define TH_LOWER   2
 
-/* ----------
- * Flags for DCH version
- * ----------
- */
-static bool DCH_global_fx = false;
-
-
 /* ----------
  * Number description struct
  * ----------
@@ -460,16 +448,9 @@ do { \
 } while(0)
 
 /*****************************************************************************
- *         KeyWords definition & action
+ *         KeyWord definitions
  *****************************************************************************/
 
-static int dch_global(int arg, char *inout, int suf, bool is_to_char,
-          bool is_interval, FormatNode *node, void *data);
-static int dch_time(int arg, char *inout, int suf, bool is_to_char,
-        bool is_interval, FormatNode *node, void *data);
-static int dch_date(int arg, char *inout, int suf, bool is_to_char,
-        bool is_interval, FormatNode *node, void *data);
-
 /* ----------
  * Suffixes:
  * ----------
@@ -684,147 +665,150 @@ typedef enum
  * ----------
  */
 static const KeyWord DCH_keywords[] = {
-/* keyword, len, func, type, isitdigit          is in Index */
-   {"A.D.", 4, dch_date, DCH_A_D, FALSE},      /* A */
-   {"A.M.", 4, dch_time, DCH_A_M, FALSE},
-   {"AD", 2, dch_date, DCH_AD, FALSE},
-   {"AM", 2, dch_time, DCH_AM, FALSE},
-   {"B.C.", 4, dch_date, DCH_B_C, FALSE},      /* B */
-   {"BC", 2, dch_date, DCH_BC, FALSE},
-   {"CC", 2, dch_date, DCH_CC, TRUE},  /* C */
-   {"DAY", 3, dch_date, DCH_DAY, FALSE},       /* D */
-   {"DDD", 3, dch_date, DCH_DDD, TRUE},
-   {"DD", 2, dch_date, DCH_DD, TRUE},
-   {"DY", 2, dch_date, DCH_DY, FALSE},
-   {"Day", 3, dch_date, DCH_Day, FALSE},
-   {"Dy", 2, dch_date, DCH_Dy, FALSE},
-   {"D", 1, dch_date, DCH_D, TRUE},
-   {"FX", 2, dch_global, DCH_FX, FALSE},       /* F */
-   {"HH24", 4, dch_time, DCH_HH24, TRUE},      /* H */
-   {"HH12", 4, dch_time, DCH_HH12, TRUE},
-   {"HH", 2, dch_time, DCH_HH, TRUE},
-   {"IDDD", 4, dch_date, DCH_IDDD, TRUE},      /* I */
-   {"ID", 2, dch_date, DCH_ID, TRUE},
-   {"IW", 2, dch_date, DCH_IW, TRUE},
-   {"IYYY", 4, dch_date, DCH_IYYY, TRUE},
-   {"IYY", 3, dch_date, DCH_IYY, TRUE},
-   {"IY", 2, dch_date, DCH_IY, TRUE},
-   {"I", 1, dch_date, DCH_I, TRUE},
-   {"J", 1, dch_date, DCH_J, TRUE},    /* J */
-   {"MI", 2, dch_time, DCH_MI, TRUE},  /* M */
-   {"MM", 2, dch_date, DCH_MM, TRUE},
-   {"MONTH", 5, dch_date, DCH_MONTH, FALSE},
-   {"MON", 3, dch_date, DCH_MON, FALSE},
-   {"MS", 2, dch_time, DCH_MS, TRUE},
-   {"Month", 5, dch_date, DCH_Month, FALSE},
-   {"Mon", 3, dch_date, DCH_Mon, FALSE},
-   {"P.M.", 4, dch_time, DCH_P_M, FALSE},      /* P */
-   {"PM", 2, dch_time, DCH_PM, FALSE},
-   {"Q", 1, dch_date, DCH_Q, TRUE},    /* Q */
-   {"RM", 2, dch_date, DCH_RM, FALSE}, /* R */
-   {"SSSS", 4, dch_time, DCH_SSSS, TRUE},      /* S */
-   {"SS", 2, dch_time, DCH_SS, TRUE},
-   {"TZ", 2, dch_time, DCH_TZ, FALSE}, /* T */
-   {"US", 2, dch_time, DCH_US, TRUE},  /* U */
-   {"WW", 2, dch_date, DCH_WW, TRUE},  /* W */
-   {"W", 1, dch_date, DCH_W, TRUE},
-   {"Y,YYY", 5, dch_date, DCH_Y_YYY, TRUE},    /* Y */
-   {"YYYY", 4, dch_date, DCH_YYYY, TRUE},
-   {"YYY", 3, dch_date, DCH_YYY, TRUE},
-   {"YY", 2, dch_date, DCH_YY, TRUE},
-   {"Y", 1, dch_date, DCH_Y, TRUE},
-   {"a.d.", 4, dch_date, DCH_a_d, FALSE},      /* a */
-   {"a.m.", 4, dch_time, DCH_a_m, FALSE},
-   {"ad", 2, dch_date, DCH_ad, FALSE},
-   {"am", 2, dch_time, DCH_am, FALSE},
-   {"b.c.", 4, dch_date, DCH_b_c, FALSE},      /* b */
-   {"bc", 2, dch_date, DCH_bc, FALSE},
-   {"cc", 2, dch_date, DCH_CC, TRUE},  /* c */
-   {"day", 3, dch_date, DCH_day, FALSE},       /* d */
-   {"ddd", 3, dch_date, DCH_DDD, TRUE},
-   {"dd", 2, dch_date, DCH_DD, TRUE},
-   {"dy", 2, dch_date, DCH_dy, FALSE},
-   {"d", 1, dch_date, DCH_D, TRUE},
-   {"fx", 2, dch_global, DCH_FX, FALSE},       /* f */
-   {"hh24", 4, dch_time, DCH_HH24, TRUE},      /* h */
-   {"hh12", 4, dch_time, DCH_HH12, TRUE},
-   {"hh", 2, dch_time, DCH_HH, TRUE},
-   {"iddd", 4, dch_date, DCH_IDDD, TRUE},      /* i */
-   {"id", 2, dch_date, DCH_ID, TRUE},
-   {"iw", 2, dch_date, DCH_IW, TRUE},
-   {"iyyy", 4, dch_date, DCH_IYYY, TRUE},
-   {"iyy", 3, dch_date, DCH_IYY, TRUE},
-   {"iy", 2, dch_date, DCH_IY, TRUE},
-   {"i", 1, dch_date, DCH_I, TRUE},
-   {"j", 1, dch_time, DCH_J, TRUE},    /* j */
-   {"mi", 2, dch_time, DCH_MI, TRUE},  /* m */
-   {"mm", 2, dch_date, DCH_MM, TRUE},
-   {"month", 5, dch_date, DCH_month, FALSE},
-   {"mon", 3, dch_date, DCH_mon, FALSE},
-   {"ms", 2, dch_time, DCH_MS, TRUE},
-   {"p.m.", 4, dch_time, DCH_p_m, FALSE},      /* p */
-   {"pm", 2, dch_time, DCH_pm, FALSE},
-   {"q", 1, dch_date, DCH_Q, TRUE},    /* q */
-   {"rm", 2, dch_date, DCH_rm, FALSE}, /* r */
-   {"ssss", 4, dch_time, DCH_SSSS, TRUE},      /* s */
-   {"ss", 2, dch_time, DCH_SS, TRUE},
-   {"tz", 2, dch_time, DCH_tz, FALSE}, /* t */
-   {"us", 2, dch_time, DCH_US, TRUE},  /* u */
-   {"ww", 2, dch_date, DCH_WW, TRUE},  /* w */
-   {"w", 1, dch_date, DCH_W, TRUE},
-   {"y,yyy", 5, dch_date, DCH_Y_YYY, TRUE},    /* y */
-   {"yyyy", 4, dch_date, DCH_YYYY, TRUE},
-   {"yyy", 3, dch_date, DCH_YYY, TRUE},
-   {"yy", 2, dch_date, DCH_YY, TRUE},
-   {"y", 1, dch_date, DCH_Y, TRUE},
-/* last */
-{NULL, 0, NULL, 0}};
+/* 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},
+
+   /* last */
+   {NULL, 0, 0, 0}
+};
 
 /* ----------
- * KeyWords for NUMBER version (now, isitdigit info is not needful here..)
+ * KeyWords for NUMBER version (is_digit field is not needful here...)
  * ----------
  */
 static const KeyWord NUM_keywords[] = {
-/* keyword,    len, func.  type               is in Index */
-   {",", 1, NULL, NUM_COMMA},  /* , */
-   {".", 1, NULL, NUM_DEC},    /* . */
-   {"0", 1, NULL, NUM_0},      /* 0 */
-   {"9", 1, NULL, NUM_9},      /* 9 */
-   {"B", 1, NULL, NUM_B},      /* B */
-   {"C", 1, NULL, NUM_C},      /* C */
-   {"D", 1, NULL, NUM_D},      /* D */
-   {"E", 1, NULL, NUM_E},      /* E */
-   {"FM", 2, NULL, NUM_FM},    /* F */
-   {"G", 1, NULL, NUM_G},      /* G */
-   {"L", 1, NULL, NUM_L},      /* L */
-   {"MI", 2, NULL, NUM_MI},    /* M */
-   {"PL", 2, NULL, NUM_PL},    /* P */
-   {"PR", 2, NULL, NUM_PR},
-   {"RN", 2, NULL, NUM_RN},    /* R */
-   {"SG", 2, NULL, NUM_SG},    /* S */
-   {"SP", 2, NULL, NUM_SP},
-   {"S", 1, NULL, NUM_S},
-   {"TH", 2, NULL, NUM_TH},    /* T */
-   {"V", 1, NULL, NUM_V},      /* V */
-   {"b", 1, NULL, NUM_B},      /* b */
-   {"c", 1, NULL, NUM_C},      /* c */
-   {"d", 1, NULL, NUM_D},      /* d */
-   {"e", 1, NULL, NUM_E},      /* e */
-   {"fm", 2, NULL, NUM_FM},    /* f */
-   {"g", 1, NULL, NUM_G},      /* g */
-   {"l", 1, NULL, NUM_L},      /* l */
-   {"mi", 2, NULL, NUM_MI},    /* m */
-   {"pl", 2, NULL, NUM_PL},    /* p */
-   {"pr", 2, NULL, NUM_PR},
-   {"rn", 2, NULL, NUM_rn},    /* r */
-   {"sg", 2, NULL, NUM_SG},    /* s */
-   {"sp", 2, NULL, NUM_SP},
-   {"s", 1, NULL, NUM_S},
-   {"th", 2, NULL, NUM_th},    /* t */
-   {"v", 1, NULL, NUM_V},      /* v */
-
-/* last */
-{NULL, 0, NULL, 0}};
+/* name, len, id           is in Index */
+   {",", 1, NUM_COMMA},    /* , */
+   {".", 1, NUM_DEC},      /* . */
+   {"0", 1, NUM_0},        /* 0 */
+   {"9", 1, NUM_9},        /* 9 */
+   {"B", 1, NUM_B},        /* B */
+   {"C", 1, NUM_C},        /* C */
+   {"D", 1, NUM_D},        /* D */
+   {"E", 1, NUM_E},        /* E */
+   {"FM", 2, NUM_FM},      /* F */
+   {"G", 1, NUM_G},        /* G */
+   {"L", 1, NUM_L},        /* L */
+   {"MI", 2, NUM_MI},      /* M */
+   {"PL", 2, NUM_PL},      /* P */
+   {"PR", 2, NUM_PR},
+   {"RN", 2, NUM_RN},      /* R */
+   {"SG", 2, NUM_SG},      /* S */
+   {"SP", 2, NUM_SP},
+   {"S", 1, NUM_S},
+   {"TH", 2, NUM_TH},      /* T */
+   {"V", 1, NUM_V},        /* V */
+   {"b", 1, NUM_B},        /* b */
+   {"c", 1, NUM_C},        /* c */
+   {"d", 1, NUM_D},        /* d */
+   {"e", 1, NUM_E},        /* e */
+   {"fm", 2, NUM_FM},      /* f */
+   {"g", 1, NUM_G},        /* g */
+   {"l", 1, NUM_L},        /* l */
+   {"mi", 2, NUM_MI},      /* m */
+   {"pl", 2, NUM_PL},      /* p */
+   {"pr", 2, NUM_PR},
+   {"rn", 2, NUM_rn},      /* r */
+   {"sg", 2, NUM_SG},      /* s */
+   {"sp", 2, NUM_SP},
+   {"s", 1, NUM_S},
+   {"th", 2, NUM_th},      /* t */
+   {"v", 1, NUM_V},        /* v */
+
+   /* last */
+   {NULL, 0, 0}
+};
 
 
 /* ----------
@@ -848,7 +832,7 @@ static const int DCH_index[KeyWord_INDEX_SIZE] = {
    -1, -1, DCH_p_m, DCH_q, DCH_rm, DCH_ssss, DCH_tz, DCH_us, -1, DCH_ww,
    -1, DCH_y_yyy, -1, -1, -1, -1
 
-   /*---- chars over 126 are skiped ----*/
+   /*---- chars over 126 are skipped ----*/
 };
 
 /* ----------
@@ -859,7 +843,7 @@ static const int NUM_index[KeyWord_INDEX_SIZE] = {
 /*
 0  1   2   3   4   5   6   7   8   9
 */
-   /*---- first 0..31 chars are skiped ----*/
+   /*---- first 0..31 chars are skipped ----*/
 
    -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, NUM_COMMA, -1, NUM_DEC, -1, NUM_0, -1,
@@ -872,7 +856,7 @@ static const int NUM_index[KeyWord_INDEX_SIZE] = {
    -1, -1, NUM_pl, -1, NUM_rn, NUM_sg, NUM_th, -1, NUM_v, -1,
    -1, -1, -1, -1, -1, -1
 
-   /*---- chars over 126 are skiped ----*/
+   /*---- chars over 126 are skipped ----*/
 };
 
 /* ----------
@@ -919,8 +903,10 @@ static KeySuffix *suff_search(char *str, KeySuffix *suf, int type);
 static void NUMDesc_prepare(NUMDesc *num, FormatNode *n);
 static void parse_format(FormatNode *node, char *str, const KeyWord *kw,
             KeySuffix *suf, const int *index, int ver, NUMDesc *Num);
-static char *DCH_processor(FormatNode *node, char *inout, bool is_to_char,
-             bool is_interval, void *data);
+
+static void DCH_to_char(FormatNode *node, bool is_interval,
+                       TmToChar *in, char *out);
+static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out);
 
 #ifdef DEBUG_TO_FROM_CHAR
 static void dump_index(const KeyWord *k, const int *index);
@@ -934,7 +920,6 @@ static int  strdigits_len(char *str);
 static char *str_toupper(char *buff);
 static char *str_tolower(char *buff);
 
-/* static int is_acdc(char *str, int *len); */
 static int seq_search(char *name, char **array, int type, int max, int *len);
 static void do_to_timestamp(text *date_txt, text *fmt,
                struct pg_tm * tm, fsec_t *fsec);
@@ -1340,75 +1325,6 @@ parse_format(FormatNode *node, char *str, const KeyWord *kw,
    return;
 }
 
-/* ----------
- * Call keyword's function for each of (action) node in format-node tree
- * ----------
- */
-static char *
-DCH_processor(FormatNode *node, char *inout, bool is_to_char,
-             bool is_interval, void *data)
-{
-   FormatNode *n;
-   char       *s;
-
-   /*
-    * Zeroing global flags
-    */
-   DCH_global_fx = false;
-
-   for (n = node, s = inout; n->type != NODE_TYPE_END; n++)
-   {
-       if (!is_to_char && *s == '\0')
-
-           /*
-            * The input string is shorter than format picture, so it's good
-            * time to break this loop...
-            *
-            * Note: this isn't relevant for TO_CHAR mode, because it uses
-            * 'inout' allocated by format picture length.
-            */
-           break;
-
-       if (n->type == NODE_TYPE_ACTION)
-       {
-           int         len;
-
-           /*
-            * Call node action function
-            */
-           len = n->key->action(n->key->id, s, n->suffix, is_to_char,
-                                is_interval, n, data);
-           if (len > 0)
-               s += len - 1;   /* s++ is at the end of the loop */
-           else if (len == -1)
-               continue;
-       }
-       else
-       {
-           /*
-            * Remove to output char from input in TO_CHAR
-            */
-           if (is_to_char)
-               *s = n->character;
-           else
-           {
-               /*
-                * Skip blank space in FROM_CHAR's input
-                */
-               if (isspace((unsigned char) n->character) && !DCH_global_fx)
-                   while (*s != '\0' && isspace((unsigned char) *(s + 1)))
-                       ++s;
-           }
-       }
-       ++s;
-   }
-
-   if (is_to_char)
-       *s = '\0';
-   return inout;
-}
-
-
 /* ----------
  * DEBUG: Dump the FormatNode Tree (debug)
  * ----------
@@ -1722,20 +1638,6 @@ dump_index(const KeyWord *k, const int *index)
  */
 #define SKIP_THth(_suf)        (S_THth(_suf) ? 2 : 0)
 
-
-/* ----------
- * Global format option for DCH version
- * ----------
- */
-static int
-dch_global(int arg, char *inout, int suf, bool is_to_char, bool is_interval,
-          FormatNode *node, void *data)
-{
-   if (arg == DCH_FX)
-       DCH_global_fx = true;
-   return -1;
-}
-
 /* ----------
  * Return TRUE if next format picture is not digit value
  * ----------
@@ -1759,7 +1661,7 @@ is_next_separator(FormatNode *n)
 
    if (n->type == NODE_TYPE_ACTION)
    {
-       if (n->key->isitdigit)
+       if (n->key->is_digit)
            return FALSE;
 
        return TRUE;
@@ -1804,1031 +1706,912 @@ 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)
+
 /* ----------
- * Master function of TIME for:
- *           TO_CHAR   - write (inout) formated string
- *           FROM_CHAR - scan (inout) string by course of FormatNode
+ * Process a TmToChar struct as denoted by a list of FormatNodes.
+ * The formatted data is written to the string pointed to by 'out'.
  * ----------
  */
-static int
-dch_time(int arg, char *inout, int suf, bool is_to_char, bool is_interval,
-        FormatNode *node, void *data)
+static void
+DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out)
 {
-   char       *p_inout = inout;
-   struct pg_tm *tm = NULL;
-   TmFromChar *tmfc = NULL;
-   TmToChar   *tmtc = NULL;
+   FormatNode *n;
+   char       *s;
+   struct pg_tm *tm = &in->tm;
+   char        buff[DCH_CACHE_SIZE],
+               workbuff[32];
+   int         i;
 
-   if (is_to_char)
+   s = out;
+   for (n = node; n->type != NODE_TYPE_END; n++)
    {
-       tmtc = (TmToChar *) data;
-       tm = tmtcTm(tmtc);
-   }
-   else
-       tmfc = (TmFromChar *) data;
+       if (n->type != NODE_TYPE_ACTION)
+       {
+           *s = n->character;
+           s++;
+           continue;
+       }
 
-   switch (arg)
-   {
-       case DCH_A_M:
-       case DCH_P_M:
-           if (is_to_char)
-           {
-               strcpy(inout, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
+       switch (n->key->id)
+       {
+           case DCH_A_M:
+           case DCH_P_M:
+               strcpy(s, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
                       ? P_M_STR : A_M_STR);
-               return strlen(p_inout);
-           }
-           else
-           {
-               if (strncmp(inout, P_M_STR, 4) == 0)
-                   tmfc->pm = TRUE;
-               else if (strncmp(inout, A_M_STR, 4) == 0)
-                   tmfc->am = TRUE;
-               else
-                   AMPM_ERROR;
-               return strlen(P_M_STR);
-           }
-           break;
-       case DCH_AM:
-       case DCH_PM:
-           if (is_to_char)
-           {
-               strcpy(inout, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
+               s += strlen(s);
+               break;
+           case DCH_AM:
+           case DCH_PM:
+               strcpy(s, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
                       ? PM_STR : AM_STR);
-               return strlen(p_inout);
-           }
-           else
-           {
-               if (strncmp(inout, PM_STR, 2) == 0)
-                   tmfc->pm = TRUE;
-               else if (strncmp(inout, AM_STR, 2) == 0)
-                   tmfc->am = TRUE;
-               else
-                   AMPM_ERROR;
-               return strlen(PM_STR);
-           }
-           break;
-       case DCH_a_m:
-       case DCH_p_m:
-           if (is_to_char)
-           {
-               strcpy(inout, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
+               s += strlen(s);
+               break;
+           case DCH_a_m:
+           case DCH_p_m:
+               strcpy(s, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
                       ? p_m_STR : a_m_STR);
-               return strlen(p_inout);
-           }
-           else
-           {
-               if (strncmp(inout, p_m_STR, 4) == 0)
-                   tmfc->pm = TRUE;
-               else if (strncmp(inout, a_m_STR, 4) == 0)
-                   tmfc->am = TRUE;
-               else
-                   AMPM_ERROR;
-               return strlen(p_m_STR);
-           }
-           break;
-       case DCH_am:
-       case DCH_pm:
-           if (is_to_char)
-           {
-               strcpy(inout, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
+               s += strlen(s);
+               break;
+           case DCH_am:
+           case DCH_pm:
+               strcpy(s, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
                       ? pm_STR : am_STR);
-               return strlen(p_inout);
-           }
-           else
-           {
-               if (strncmp(inout, pm_STR, 2) == 0)
-                   tmfc->pm = TRUE;
-               else if (strncmp(inout, am_STR, 2) == 0)
-                   tmfc->am = TRUE;
-               else
-                   AMPM_ERROR;
-               return strlen(pm_STR);
-           }
-           break;
-       case DCH_HH:
-       case DCH_HH12:
-           if (is_to_char)
-           {
-               sprintf(inout, "%0*d", S_FM(suf) ? 0 : 2,
+               s += strlen(s);
+               break;
+           case DCH_HH:
+           case DCH_HH12:
+               sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 2,
                        tm->tm_hour % (HOURS_PER_DAY / 2) == 0 ? 12 :
                        tm->tm_hour % (HOURS_PER_DAY / 2));
-               if (S_THth(suf))
-                   str_numth(p_inout, inout, 0);
-               return strlen(p_inout);
-           }
-           else
-           {
-               if (S_FM(suf) || is_next_separator(node))
+               if (S_THth(n->suffix))
+                   str_numth(s, s, 0);
+               s += strlen(s);
+               break;
+           case DCH_HH24:
+               sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 2, tm->tm_hour);
+               if (S_THth(n->suffix))
+                   str_numth(s, s, S_TH_TYPE(n->suffix));
+               s += strlen(s);
+               break;
+           case DCH_MI:
+               sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 2, tm->tm_min);
+               if (S_THth(n->suffix))
+                   str_numth(s, s, S_TH_TYPE(n->suffix));
+               s += strlen(s);
+               break;
+           case DCH_SS:
+               sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 2, tm->tm_sec);
+               if (S_THth(n->suffix))
+                   str_numth(s, s, S_TH_TYPE(n->suffix));
+               s += strlen(s);
+               break;
+           case DCH_MS:            /* millisecond */
+#ifdef HAVE_INT64_TIMESTAMP
+               sprintf(s, "%03d", (int) (in->fsec / INT64CONST(1000)));
+#else
+               /* No rint() because we can't overflow and we might print US */
+               sprintf(s, "%03d", (int) (in->fsec * 1000));
+#endif
+               if (S_THth(n->suffix))
+                   str_numth(s, s, S_TH_TYPE(n->suffix));
+               s += strlen(s);
+               break;
+           case DCH_US:            /* microsecond */
+#ifdef HAVE_INT64_TIMESTAMP
+               sprintf(s, "%06d", (int) in->fsec);
+#else
+               /* don't use rint() because we can't overflow 1000 */
+               sprintf(s, "%06d", (int) (in->fsec * 1000000));
+#endif
+               if (S_THth(n->suffix))
+                   str_numth(s, s, S_TH_TYPE(n->suffix));
+               s += strlen(s);
+               break;
+           case DCH_SSSS:
+               sprintf(s, "%d", 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))
+               {
+                   char       *p = pstrdup(tmtcTzn(in));
+
+                   strcpy(s, str_tolower(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_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))
                {
-                   sscanf(inout, "%d", &tmfc->hh);
-                   return strdigits_len(inout) + SKIP_THth(suf);
+                   strcpy(workbuff, localize_month_full(tm->tm_mon - 1));
+                   sprintf(s, "%*s", 0, localized_str_toupper(workbuff));
                }
                else
                {
-                   sscanf(inout, "%02d", &tmfc->hh);
-                   return strspace_len(inout) + 2 + SKIP_THth(suf);
+                   strcpy(workbuff, months_full[tm->tm_mon - 1]);
+                   sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, str_toupper(workbuff));
                }
-           }
-           break;
-       case DCH_HH24:
-           if (is_to_char)
-           {
-               sprintf(inout, "%0*d", S_FM(suf) ? 0 : 2, tm->tm_hour);
-               if (S_THth(suf))
-                   str_numth(p_inout, inout, S_TH_TYPE(suf));
-               return strlen(p_inout);
-           }
-           else
-           {
-               if (S_FM(suf) || is_next_separator(node))
+               s += strlen(s);
+               break;
+           case DCH_Month:
+               INVALID_FOR_INTERVAL;
+               if (!tm->tm_mon)
+                   break;
+               if (S_TM(n->suffix))
+                   sprintf(s, "%*s", 0, localize_month_full(tm->tm_mon - 1));
+               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))
                {
-                   sscanf(inout, "%d", &tmfc->hh);
-                   return strdigits_len(inout) + SKIP_THth(suf);
+                   strcpy(workbuff, localize_month_full(tm->tm_mon - 1));
+                   sprintf(s, "%*s", 0, localized_str_tolower(workbuff));
                }
                else
                {
-                   sscanf(inout, "%02d", &tmfc->hh);
-                   return strspace_len(inout) + 2 + SKIP_THth(suf);
+                   sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, months_full[tm->tm_mon - 1]);
+                   *s = pg_tolower((unsigned char) *s);
                }
-           }
-           break;
-       case DCH_MI:
-           if (is_to_char)
-           {
-               sprintf(inout, "%0*d", S_FM(suf) ? 0 : 2, tm->tm_min);
-               if (S_THth(suf))
-                   str_numth(p_inout, inout, S_TH_TYPE(suf));
-               return strlen(p_inout);
-           }
-           else
-           {
-               if (S_FM(suf) || is_next_separator(node))
+               s += strlen(s);
+               break;
+           case DCH_MON:
+               INVALID_FOR_INTERVAL;
+               if (!tm->tm_mon)
+                   break;
+               if (S_TM(n->suffix))
                {
-                   sscanf(inout, "%d", &tmfc->mi);
-                   return strdigits_len(inout) + SKIP_THth(suf);
+                   strcpy(workbuff, localize_month(tm->tm_mon - 1));
+                   strcpy(s, localized_str_toupper(workbuff));
                }
                else
                {
-                   sscanf(inout, "%02d", &tmfc->mi);
-                   return strspace_len(inout) + 2 + SKIP_THth(suf);
+                   strcpy(s, months[tm->tm_mon - 1]);
+                   str_toupper(s);
                }
-           }
-           break;
-       case DCH_SS:
-           if (is_to_char)
-           {
-               sprintf(inout, "%0*d", S_FM(suf) ? 0 : 2, tm->tm_sec);
-               if (S_THth(suf))
-                   str_numth(p_inout, inout, S_TH_TYPE(suf));
-               return strlen(p_inout);
-           }
-           else
-           {
-               if (S_FM(suf) || is_next_separator(node))
+               s += strlen(s);
+               break;
+           case DCH_Mon:
+               INVALID_FOR_INTERVAL;
+               if (!tm->tm_mon)
+                   break;
+               if (S_TM(n->suffix))
+                   strcpy(s, localize_month(tm->tm_mon - 1));
+               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))
                {
-                   sscanf(inout, "%d", &tmfc->ss);
-                   return strdigits_len(inout) + SKIP_THth(suf);
+                   strcpy(workbuff, localize_month(tm->tm_mon - 1));
+                   strcpy(s, localized_str_tolower(workbuff));
                }
                else
                {
-                   sscanf(inout, "%02d", &tmfc->ss);
-                   return strspace_len(inout) + 2 + SKIP_THth(suf);
+                   strcpy(s, months[tm->tm_mon - 1]);
+                   *s = pg_tolower((unsigned char) *s);
                }
-           }
-           break;
-       case DCH_MS:            /* millisecond */
-           if (is_to_char)
-           {
-#ifdef HAVE_INT64_TIMESTAMP
-               sprintf(inout, "%03d", (int) (tmtc->fsec / INT64CONST(1000)));
-#else
-               /* No rint() because we can't overflow and we might print US */
-               sprintf(inout, "%03d", (int) (tmtc->fsec * 1000));
-#endif
-               if (S_THth(suf))
-                   str_numth(p_inout, inout, S_TH_TYPE(suf));
-               return strlen(p_inout);
-           }
-           else
-           {
-               int         len,
-                           x;
-
-               if (is_next_separator(node))
+               s += strlen(s);
+               break;
+           case DCH_MM:
+               sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 2, 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))
                {
-                   sscanf(inout, "%d", &tmfc->ms);
-                   len = x = strdigits_len(inout);
+                   strcpy(workbuff, localize_day_full(tm->tm_wday));
+                   sprintf(s, "%*s", 0, localized_str_toupper(workbuff));
                }
                else
                {
-                   sscanf(inout, "%03d", &tmfc->ms);
-                   x = strdigits_len(inout);
-                   len = x = x > 3 ? 3 : x;
+                   strcpy(workbuff, days[tm->tm_wday]);
+                   sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, str_toupper(workbuff));
                }
-
-               /*
-                * 25 is 0.25 and 250 is 0.25 too; 025 is 0.025 and not 0.25
-                */
-               tmfc->ms *= x == 1 ? 100 :
-                   x == 2 ? 10 : 1;
-
-               /*
-                * elog(DEBUG3, "X: %d, MS: %d, LEN: %d", x, tmfc->ms, len);
-                */
-               return len + SKIP_THth(suf);
-           }
-           break;
-       case DCH_US:            /* microsecond */
-           if (is_to_char)
-           {
-#ifdef HAVE_INT64_TIMESTAMP
-               sprintf(inout, "%06d", (int) tmtc->fsec);
-#else
-               /* don't use rint() because we can't overflow 1000 */
-               sprintf(inout, "%06d", (int) (tmtc->fsec * 1000000));
-#endif
-               if (S_THth(suf))
-                   str_numth(p_inout, inout, S_TH_TYPE(suf));
-               return strlen(p_inout);
-           }
-           else
-           {
-               int         len,
-                           x;
-
-               if (is_next_separator(node))
+               s += strlen(s);
+               break;
+           case DCH_Day:
+               INVALID_FOR_INTERVAL;
+               if (S_TM(n->suffix))
+                   sprintf(s, "%*s", 0, localize_day_full(tm->tm_wday));
+               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))
                {
-                   sscanf(inout, "%d", &tmfc->us);
-                   len = x = strdigits_len(inout);
+                   strcpy(workbuff, localize_day_full(tm->tm_wday));
+                   sprintf(s, "%*s", 0, localized_str_tolower(workbuff));
                }
                else
                {
-                   sscanf(inout, "%06d", &tmfc->us);
-                   x = strdigits_len(inout);
-                   len = x = x > 6 ? 6 : x;
+                   sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, days[tm->tm_wday]);
+                   *s = pg_tolower((unsigned char) *s);
                }
-
-               tmfc->us *= x == 1 ? 100000 :
-                   x == 2 ? 10000 :
-                   x == 3 ? 1000 :
-                   x == 4 ? 100 :
-                   x == 5 ? 10 : 1;
-
-               /*
-                * elog(DEBUG3, "X: %d, US: %d, LEN: %d", x, tmfc->us, len);
-                */
-               return len + SKIP_THth(suf);
-           }
-           break;
-       case DCH_SSSS:
-           if (is_to_char)
-           {
-               sprintf(inout, "%d", tm->tm_hour * SECS_PER_HOUR +
-                       tm->tm_min * SECS_PER_MINUTE +
-                       tm->tm_sec);
-               if (S_THth(suf))
-                   str_numth(p_inout, inout, S_TH_TYPE(suf));
-               return strlen(p_inout);
-           }
-           else
-           {
-               if (S_FM(suf) || is_next_separator(node))
+               s += strlen(s);
+               break;
+           case DCH_DY:
+               INVALID_FOR_INTERVAL;
+               if (S_TM(n->suffix))
                {
-                   sscanf(inout, "%d", &tmfc->ssss);
-                   return strdigits_len(inout) + SKIP_THth(suf);
+                   strcpy(workbuff, localize_day(tm->tm_wday));
+                   strcpy(s, localized_str_toupper(workbuff));
                }
                else
                {
-                   sscanf(inout, "%05d", &tmfc->ssss);
-                   return strspace_len(inout) + 5 + SKIP_THth(suf);
+                   strcpy(s, days_short[tm->tm_wday]);
+                   str_toupper(s);
                }
-           }
-           break;
-       case DCH_tz:
-       case DCH_TZ:
-           INVALID_FOR_INTERVAL;
-           if (is_to_char && tmtcTzn(tmtc))
-           {
-               if (arg == DCH_TZ)
-                   strcpy(inout, tmtcTzn(tmtc));
+               s += strlen(s);
+               break;
+           case DCH_Dy:
+               INVALID_FOR_INTERVAL;
+               if (S_TM(n->suffix))
+                   strcpy(s, localize_day(tm->tm_wday));
                else
+                   strcpy(s, days_short[tm->tm_wday]);
+               s += strlen(s);
+               break;
+           case DCH_dy:
+               INVALID_FOR_INTERVAL;
+               if (S_TM(n->suffix))
                {
-                   char       *p = pstrdup(tmtcTzn(tmtc));
-
-                   strcpy(inout, str_tolower(p));
-                   pfree(p);
+                   strcpy(workbuff, localize_day(tm->tm_wday));
+                   strcpy(s, localized_str_tolower(workbuff));
                }
-               return strlen(inout);
-           }
-           else if (!is_to_char)
-               ereport(ERROR,
-                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                        errmsg("\"TZ\"/\"tz\" not supported")));
+               else
+               {
+                   strcpy(s, days_short[tm->tm_wday]);
+                   *s = pg_tolower((unsigned char) *s);
+               }
+               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                        /* century 21 starts in 2001 */
+                   i = (tm->tm_year - 1) / 100 + 1;
+               if (i <= 99 && i >= -99)
+                   sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 2, 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:
+               if (tm->tm_year <= 9999 && tm->tm_year >= -9998)
+                   sprintf(s, "%0*d",
+                           S_FM(n->suffix) ? 0 : 4,
+                           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));
+               else
+                   sprintf(s, "%d",
+                           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:
+               snprintf(buff, sizeof(buff), "%03d",
+                        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));
+               i = strlen(buff);
+               strcpy(s, buff + (i - 3));
+               if (S_THth(n->suffix))
+                   str_numth(s, s, S_TH_TYPE(n->suffix));
+               s += strlen(s);
+               break;
+           case DCH_YY:
+           case DCH_IY:
+               snprintf(buff, sizeof(buff), "%02d",
+                        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));
+               i = strlen(buff);
+               strcpy(s, buff + (i - 2));
+               if (S_THth(n->suffix))
+                   str_numth(s, s, S_TH_TYPE(n->suffix));
+               s += strlen(s);
+               break;
+           case DCH_Y:
+           case DCH_I:
+               snprintf(buff, sizeof(buff), "%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));
+               i = strlen(buff);
+               strcpy(s, buff + (i - 1));
+               if (S_THth(n->suffix))
+                   str_numth(s, s, S_TH_TYPE(n->suffix));
+               s += strlen(s);
+               break;
+           case DCH_RM:
+               if (!tm->tm_mon)
+                   break;
+               sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -4,
+                       rm_months_upper[12 - tm->tm_mon]);
+               s += strlen(s);
+               break;
+           case DCH_rm:
+               if (!tm->tm_mon)
+                   break;
+               sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -4,
+                       rm_months_lower[12 - tm->tm_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;
+       }
    }
-   return -1;
-}
-
-#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)
 
+   *s = '\0';
+}
 
 /* ----------
- * Master of DATE for:
- *       TO_CHAR - write (inout) formated string
- *       FROM_CHAR - scan (inout) string by course of FormatNode
+ * Process a string as denoted by a list of FormatNodes.
+ * The TmFromChar struct pointed to by 'out' is populated with the results.
+ *
+ * Note: we currently don't have any to_interval() function, so there
+ * is no need here for INVALID_FOR_INTERVAL checks.
  * ----------
  */
-static int
-dch_date(int arg, char *inout, int suf, bool is_to_char, bool is_interval,
-        FormatNode *node, void *data)
+static void
+DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 {
-   char        buff[DCH_CACHE_SIZE],
-               workbuff[32],
-              *p_inout = inout;
-   int         i,
-               len;
-   struct pg_tm *tm = NULL;
-   TmFromChar *tmfc = NULL;
-   TmToChar   *tmtc = NULL;
+   FormatNode *n;
+   char       *s;
+   int         len,
+               x;
+   int        *target;
+   bool        fx_mode = false;
 
-   if (is_to_char)
+   for (n = node, s = in; n->type != NODE_TYPE_END && *s != '\0'; n++)
    {
-       tmtc = (TmToChar *) data;
-       tm = tmtcTm(tmtc);
-   }
-   else
-       tmfc = (TmFromChar *) data;
-
-   /*
-    * In the FROM-char there is no difference between "January" or "JANUARY"
-    * or "january", all is before search convert to "first-upper". This
-    * convention is used for MONTH, MON, DAY, DY
-    */
-   if (!is_to_char)
-   {
-       if (arg == DCH_MONTH || arg == DCH_Month || arg == DCH_month)
-       {
-           tmfc->mm = seq_search(inout, months_full, ONE_UPPER, FULL_SIZ, &len) + 1;
-           CHECK_SEQ_SEARCH(len, "MONTH/Month/month");
-           return len;
-       }
-       else if (arg == DCH_MON || arg == DCH_Mon || arg == DCH_mon)
+       if (n->type != NODE_TYPE_ACTION)
        {
-           tmfc->mm = seq_search(inout, months, ONE_UPPER, MAX_MON_LEN, &len) + 1;
-           CHECK_SEQ_SEARCH(len, "MON/Mon/mon");
-           return 3;
-       }
-       else if (arg == DCH_DAY || arg == DCH_Day || arg == DCH_day)
-       {
-           tmfc->d = seq_search(inout, days, ONE_UPPER, FULL_SIZ, &len);
-           CHECK_SEQ_SEARCH(len, "DAY/Day/day");
-           return len;
-       }
-       else if (arg == DCH_DY || arg == DCH_Dy || arg == DCH_dy)
-       {
-           tmfc->d = seq_search(inout, days, ONE_UPPER, MAX_DY_LEN, &len);
-           CHECK_SEQ_SEARCH(len, "DY/Dy/dy");
-           return 3;
-       }
-   }
-
-   switch (arg)
-   {
-       case DCH_A_D:
-       case DCH_B_C:
-           INVALID_FOR_INTERVAL;
-           if (is_to_char)
-           {
-               strcpy(inout, (tm->tm_year <= 0 ? B_C_STR : A_D_STR));
-               return strlen(p_inout);
-           }
-           else
-           {
-               if (strncmp(inout, B_C_STR, 4) == 0)
-                   tmfc->bc = TRUE;
-               return 4;
-           }
-           break;
-       case DCH_AD:
-       case DCH_BC:
-           INVALID_FOR_INTERVAL;
-           if (is_to_char)
-           {
-               strcpy(inout, (tm->tm_year <= 0 ? BC_STR : AD_STR));
-               return strlen(p_inout);
-           }
-           else
-           {
-               if (strncmp(inout, BC_STR, 2) == 0)
-                   tmfc->bc = TRUE;
-               return 2;
-           }
-           break;
-       case DCH_a_d:
-       case DCH_b_c:
-           INVALID_FOR_INTERVAL;
-           if (is_to_char)
-           {
-               strcpy(inout, (tm->tm_year <= 0 ? b_c_STR : a_d_STR));
-               return strlen(p_inout);
-           }
-           else
-           {
-               if (strncmp(inout, b_c_STR, 4) == 0)
-                   tmfc->bc = TRUE;
-               return 4;
-           }
-           break;
-       case DCH_ad:
-       case DCH_bc:
-           INVALID_FOR_INTERVAL;
-           if (is_to_char)
-           {
-               strcpy(inout, (tm->tm_year <= 0 ? bc_STR : ad_STR));
-               return strlen(p_inout);
-           }
-           else
-           {
-               if (strncmp(inout, bc_STR, 2) == 0)
-                   tmfc->bc = TRUE;
-               return 2;
-           }
-           break;
-       case DCH_MONTH:
-           INVALID_FOR_INTERVAL;
-           if (!tm->tm_mon)
-               return -1;
-           if (S_TM(suf))
-           {
-               strcpy(workbuff, localize_month_full(tm->tm_mon - 1));
-               sprintf(inout, "%*s", 0, localized_str_toupper(workbuff));
-           }
-           else
-           {
-               strcpy(workbuff, months_full[tm->tm_mon - 1]);
-               sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, str_toupper(workbuff));
-           }
-           return strlen(p_inout);
-
-       case DCH_Month:
-           INVALID_FOR_INTERVAL;
-           if (!tm->tm_mon)
-               return -1;
-           if (S_TM(suf))
-               sprintf(inout, "%*s", 0, localize_month_full(tm->tm_mon - 1));
-           else
-               sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, months_full[tm->tm_mon - 1]);
-           return strlen(p_inout);
-
-       case DCH_month:
-           INVALID_FOR_INTERVAL;
-           if (!tm->tm_mon)
-               return -1;
-           if (S_TM(suf))
-           {
-               strcpy(workbuff, localize_month_full(tm->tm_mon - 1));
-               sprintf(inout, "%*s", 0, localized_str_tolower(workbuff));
-           }
-           else
-           {
-               sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, months_full[tm->tm_mon - 1]);
-               *inout = pg_tolower((unsigned char) *inout);
-           }
-           return strlen(p_inout);
-
-       case DCH_MON:
-           INVALID_FOR_INTERVAL;
-           if (!tm->tm_mon)
-               return -1;
-           if (S_TM(suf))
-           {
-               strcpy(workbuff, localize_month(tm->tm_mon - 1));
-               strcpy(inout, localized_str_toupper(workbuff));
-           }
-           else
-           {
-               strcpy(inout, months[tm->tm_mon - 1]);
-               str_toupper(inout);
-           }
-           return strlen(p_inout);
-
-       case DCH_Mon:
-           INVALID_FOR_INTERVAL;
-           if (!tm->tm_mon)
-               return -1;
-           if (S_TM(suf))
-               strcpy(inout, localize_month(tm->tm_mon - 1));
-           else
-               strcpy(inout, months[tm->tm_mon - 1]);
-           return strlen(p_inout);
-
-       case DCH_mon:
-           INVALID_FOR_INTERVAL;
-           if (!tm->tm_mon)
-               return -1;
-           if (S_TM(suf))
+           s++;
+           /* Ignore spaces when not in FX (fixed width) mode */
+           if (isspace((unsigned char) n->character) && !fx_mode)
            {
-               strcpy(workbuff, localize_month(tm->tm_mon - 1));
-               strcpy(inout, localized_str_tolower(workbuff));
+               while (*s != '\0' && isspace((unsigned char) *s))
+                   s++;
            }
-           else
-           {
-               strcpy(inout, months[tm->tm_mon - 1]);
-               *inout = pg_tolower((unsigned char) *inout);
-           }
-           return strlen(p_inout);
+           continue;
+       }
 
-       case DCH_MM:
-           if (is_to_char)
-           {
-               sprintf(inout, "%0*d", S_FM(suf) ? 0 : 2, tm->tm_mon);
-               if (S_THth(suf))
-                   str_numth(p_inout, inout, S_TH_TYPE(suf));
-               return strlen(p_inout);
-           }
-           else
-           {
-               if (S_FM(suf) || is_next_separator(node))
+       switch (n->key->id)
+       {
+           case DCH_FX:
+               fx_mode = true;
+               break;
+           case DCH_A_M:
+           case DCH_P_M:
+               if (strncmp(s, P_M_STR, n->key->len) == 0)
+                   out->pm = TRUE;
+               else if (strncmp(s, A_M_STR, n->key->len) == 0)
+                   out->am = TRUE;
+               else
+                   AMPM_ERROR;
+               s += strlen(P_M_STR);
+               break;
+           case DCH_AM:
+           case DCH_PM:
+               if (strncmp(s, PM_STR, n->key->len) == 0)
+                   out->pm = TRUE;
+               else if (strncmp(s, AM_STR, n->key->len) == 0)
+                   out->am = TRUE;
+               else
+                   AMPM_ERROR;
+               s += strlen(PM_STR);
+               break;
+           case DCH_a_m:
+           case DCH_p_m:
+               if (strncmp(s, p_m_STR, n->key->len) == 0)
+                   out->pm = TRUE;
+               else if (strncmp(s, a_m_STR, n->key->len) == 0)
+                   out->am = TRUE;
+               else
+                   AMPM_ERROR;
+               s += strlen(p_m_STR);
+               break;
+           case DCH_am:
+           case DCH_pm:
+               if (strncmp(s, pm_STR, n->key->len) == 0)
+                   out->pm = TRUE;
+               else if (strncmp(s, am_STR, n->key->len) == 0)
+                   out->am = TRUE;
+               else
+                   AMPM_ERROR;
+               s += strlen(pm_STR);
+               break;
+           case DCH_HH:
+           case DCH_HH12:
+               if (S_FM(n->suffix) || is_next_separator(n))
                {
-                   sscanf(inout, "%d", &tmfc->mm);
-                   return strdigits_len(inout) + SKIP_THth(suf);
+                   sscanf(s, "%d", &out->hh);
+                   s += strdigits_len(s) + SKIP_THth(n->suffix);
                }
                else
                {
-                   sscanf(inout, "%02d", &tmfc->mm);
-                   return strspace_len(inout) + 2 + SKIP_THth(suf);
+                   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);
+               }
+               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);
+               }
+               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);
+               }
+               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;
                }
-           }
-           break;
-       case DCH_DAY:
-           INVALID_FOR_INTERVAL;
-           if (S_TM(suf))
-           {
-               strcpy(workbuff, localize_day_full(tm->tm_wday));
-               sprintf(inout, "%*s", 0, localized_str_toupper(workbuff));
-           }
-           else
-           {
-               strcpy(workbuff, days[tm->tm_wday]);
-               sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, str_toupper(workbuff));
-           }
-           return strlen(p_inout);
-
-       case DCH_Day:
-           INVALID_FOR_INTERVAL;
-           if (S_TM(suf))
-               sprintf(inout, "%*s", 0, localize_day_full(tm->tm_wday));
-           else
-               sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, days[tm->tm_wday]);
-           return strlen(p_inout);
-
-       case DCH_day:
-           INVALID_FOR_INTERVAL;
-           if (S_TM(suf))
-           {
-               strcpy(workbuff, localize_day_full(tm->tm_wday));
-               sprintf(inout, "%*s", 0, localized_str_tolower(workbuff));
-           }
-           else
-           {
-               sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, days[tm->tm_wday]);
-               *inout = pg_tolower((unsigned char) *inout);
-           }
-           return strlen(p_inout);
-
-       case DCH_DY:
-           INVALID_FOR_INTERVAL;
-           if (S_TM(suf))
-           {
-               strcpy(workbuff, localize_day(tm->tm_wday));
-               strcpy(inout, localized_str_toupper(workbuff));
-           }
-           else
-           {
-               strcpy(inout, days_short[tm->tm_wday]);
-               str_toupper(inout);
-           }
 
-           return strlen(p_inout);
+               /*
+                * 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;
 
-       case DCH_Dy:
-           INVALID_FOR_INTERVAL;
-           if (S_TM(suf))
-               strcpy(inout, localize_day(tm->tm_wday));
-           else
-               strcpy(inout, days_short[tm->tm_wday]);
-           return strlen(p_inout);
+               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;
+               }
 
-       case DCH_dy:
-           INVALID_FOR_INTERVAL;
-           if (S_TM(suf))
-           {
-               strcpy(workbuff, localize_day(tm->tm_wday));
-               strcpy(inout, localized_str_tolower(workbuff));
-           }
-           else
-           {
-               strcpy(inout, days_short[tm->tm_wday]);
-               *inout = pg_tolower((unsigned char) *inout);
-           }
-           return strlen(p_inout);
+               out->us *= x == 1 ? 100000 :
+                   x == 2 ? 10000 :
+                   x == 3 ? 1000 :
+                   x == 4 ? 100 :
+                   x == 5 ? 10 : 1;
 
-       case DCH_DDD:
-       case DCH_IDDD:
-           if (is_to_char)
-           {
-               sprintf(inout, "%0*d", S_FM(suf) ? 0 : 3,
-                       (arg == DCH_DDD) ?
-                       tm->tm_yday :
-                     date2isoyearday(tm->tm_year, tm->tm_mon, tm->tm_mday));
-               if (S_THth(suf))
-                   str_numth(p_inout, inout, S_TH_TYPE(suf));
-               return strlen(p_inout);
-           }
-           else
-           {
-               if (S_FM(suf) || is_next_separator(node))
+               s += len + SKIP_THth(n->suffix);
+               break;
+           case DCH_SSSS:
+               if (S_FM(n->suffix) || is_next_separator(n))
                {
-                   sscanf(inout, "%d", &tmfc->ddd);
-                   return strdigits_len(inout) + SKIP_THth(suf);
+                   sscanf(s, "%d", &out->ssss);
+                   s += strdigits_len(s) + SKIP_THth(n->suffix);
                }
                else
                {
-                   sscanf(inout, "%03d", &tmfc->ddd);
-                   return strspace_len(inout) + 3 + SKIP_THth(suf);
+                   sscanf(s, "%05d", &out->ssss);
+                   s += strspace_len(s) + 5 + SKIP_THth(n->suffix);
                }
-           }
-           break;
-       case DCH_DD:
-           if (is_to_char)
-           {
-               sprintf(inout, "%0*d", S_FM(suf) ? 0 : 2, tm->tm_mday);
-               if (S_THth(suf))
-                   str_numth(p_inout, inout, S_TH_TYPE(suf));
-               return strlen(p_inout);
-           }
-           else
-           {
-               if (S_FM(suf) || is_next_separator(node))
+               break;
+           case DCH_tz:
+           case DCH_TZ:
+               ereport(ERROR,
+                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                        errmsg("\"TZ\"/\"tz\" format patterns are not supported in to_date")));
+           case DCH_A_D:
+           case DCH_B_C:
+               if (strncmp(s, B_C_STR, n->key->len) == 0)
+                   out->bc = TRUE;
+               s += n->key->len;
+               break;
+           case DCH_AD:
+           case DCH_BC:
+               if (strncmp(s, BC_STR, n->key->len) == 0)
+                   out->bc = TRUE;
+               s += n->key->len;
+               break;
+           case DCH_a_d:
+           case DCH_b_c:
+               if (strncmp(s, b_c_STR, n->key->len) == 0)
+                   out->bc = TRUE;
+               s += n->key->len;
+               break;
+           case DCH_ad:
+           case DCH_bc:
+               if (strncmp(s, bc_STR, n->key->len) == 0)
+                   out->bc = TRUE;
+               s += n->key->len;
+               break;
+           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;
+               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;
+               break;
+           case DCH_MM:
+               if (S_FM(n->suffix) || is_next_separator(n))
                {
-                   sscanf(inout, "%d", &tmfc->dd);
-                   return strdigits_len(inout) + SKIP_THth(suf);
+                   sscanf(s, "%d", &out->mm);
+                   s += strdigits_len(s) + SKIP_THth(n->suffix);
                }
                else
                {
-                   sscanf(inout, "%02d", &tmfc->dd);
-                   return strspace_len(inout) + 2 + SKIP_THth(suf);
+                   sscanf(s, "%02d", &out->mm);
+                   s += strspace_len(s) + 2 + SKIP_THth(n->suffix);
                }
-           }
-           break;
-       case DCH_D:
-       case DCH_ID:
-           INVALID_FOR_INTERVAL;
-           if (is_to_char)
-           {
-               if (arg == DCH_D)
-                   sprintf(inout, "%d", tm->tm_wday + 1);
-               else
-                   sprintf(inout, "%d", (tm->tm_wday == 0) ? 7 : tm->tm_wday);
-               if (S_THth(suf))
-                   str_numth(p_inout, inout, S_TH_TYPE(suf));
-               return strlen(p_inout);
-           }
-           else
-           {
-               sscanf(inout, "%1d", &tmfc->d);
-               if (arg == DCH_D)
-                   tmfc->d--;
-               return strspace_len(inout) + 1 + SKIP_THth(suf);
-           }
-           break;
-       case DCH_WW:
-           if (is_to_char)
-           {
-               sprintf(inout, "%0*d", S_FM(suf) ? 0 : 2,
-                       (tm->tm_yday - 1) / 7 + 1);
-               if (S_THth(suf))
-                   str_numth(p_inout, inout, S_TH_TYPE(suf));
-               return strlen(p_inout);
-           }
-           else
-           {
-               if (S_FM(suf) || is_next_separator(node))
+               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;
+               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;
+               break;
+           case DCH_DDD:
+           case DCH_IDDD:
+               if (S_FM(n->suffix) || is_next_separator(n))
                {
-                   sscanf(inout, "%d", &tmfc->ww);
-                   return strdigits_len(inout) + SKIP_THth(suf);
+                   sscanf(s, "%d", &out->ddd);
+                   s += strdigits_len(s) + SKIP_THth(n->suffix);
                }
                else
                {
-                   sscanf(inout, "%02d", &tmfc->ww);
-                   return strspace_len(inout) + 2 + SKIP_THth(suf);
+                   sscanf(s, "%03d", &out->ddd);
+                   s += strspace_len(s) + 3 + SKIP_THth(n->suffix);
                }
-           }
-           break;
-       case DCH_IW:
-           if (is_to_char)
-           {
-               sprintf(inout, "%0*d", S_FM(suf) ? 0 : 2,
-                       date2isoweek(tm->tm_year, tm->tm_mon, tm->tm_mday));
-               if (S_THth(suf))
-                   str_numth(p_inout, inout, S_TH_TYPE(suf));
-               return strlen(p_inout);
-           }
-           else
-           {
-               if (S_FM(suf) || is_next_separator(node))
+               break;
+           case DCH_DD:
+               if (S_FM(n->suffix) || is_next_separator(n))
                {
-                   sscanf(inout, "%d", &tmfc->iw);
-                   return strdigits_len(inout) + SKIP_THth(suf);
+                   sscanf(s, "%d", &out->dd);
+                   s += strdigits_len(s) + SKIP_THth(n->suffix);
                }
                else
                {
-                   sscanf(inout, "%02d", &tmfc->iw);
-                   return strspace_len(inout) + 2 + SKIP_THth(suf);
+                   sscanf(s, "%02d", &out->dd);
+                   s += strspace_len(s) + 2 + SKIP_THth(n->suffix);
+               }
+               break;
+           case DCH_D:
+           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);
+               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);
                }
-           }
-           break;
-       case DCH_Q:
-           if (is_to_char)
-           {
-               if (!tm->tm_mon)
-                   return -1;
-               sprintf(inout, "%d", (tm->tm_mon - 1) / 3 + 1);
-               if (S_THth(suf))
-                   str_numth(p_inout, inout, S_TH_TYPE(suf));
-               return strlen(p_inout);
-           }
-           else
-           {
-               sscanf(inout, "%1d", &tmfc->q);
-               return strspace_len(inout) + 1 + SKIP_THth(suf);
-           }
-           break;
-       case DCH_CC:
-           if (is_to_char)
-           {
-               if (is_interval)    /* straight calculation */
-                   i = tm->tm_year / 100;
-               else    /* century 21 starts in 2001 */
-                   i = (tm->tm_year - 1) / 100 + 1;
-               if (i <= 99 && i >= -99)
-                   sprintf(inout, "%0*d", S_FM(suf) ? 0 : 2, i);
                else
-                   sprintf(inout, "%d", i);
-               if (S_THth(suf))
-                   str_numth(p_inout, inout, S_TH_TYPE(suf));
-               return strlen(p_inout);
-           }
-           else
-           {
-               if (S_FM(suf) || is_next_separator(node))
                {
-                   sscanf(inout, "%d", &tmfc->cc);
-                   return strdigits_len(inout) + SKIP_THth(suf);
+                   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(inout, "%02d", &tmfc->cc);
-                   return strspace_len(inout) + 2 + SKIP_THth(suf);
+                   sscanf(s, "%02d", &out->iw);
+                   s += strspace_len(s) + 2 + SKIP_THth(n->suffix);
+               }
+               break;
+           case DCH_Q:
+               /*
+                * We ignore Q when converting to date because it is not
+                * normative.
+                */
+               s += strspace_len(s) + 1 + 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);
                }
-           }
-           break;
-       case DCH_Y_YYY:
-           if (is_to_char)
-           {
-               i = ADJUST_YEAR(tm->tm_year, is_interval) / 1000;
-               sprintf(inout, "%d,%03d", i, ADJUST_YEAR(tm->tm_year, is_interval) - (i * 1000));
-               if (S_THth(suf))
-                   str_numth(p_inout, inout, S_TH_TYPE(suf));
-               return strlen(p_inout);
-           }
-           else
-           {
-               int         cc;
-
-               sscanf(inout, "%d,%03d", &cc, &tmfc->year);
-               tmfc->year += (cc * 1000);
-               tmfc->yysz = 4;
-               return strdigits_len(inout) + 4 + SKIP_THth(suf);
-           }
-           break;
-       case DCH_YYYY:
-       case DCH_IYYY:
-           if (is_to_char)
-           {
-               if (tm->tm_year <= 9999 && tm->tm_year >= -9998)
-                   sprintf(inout, "%0*d",
-                           S_FM(suf) ? 0 : 4,
-                           arg == DCH_YYYY ?
-                           ADJUST_YEAR(tm->tm_year, is_interval) :
-                           ADJUST_YEAR(date2isoyear(
-                                                    tm->tm_year,
-                                                    tm->tm_mon,
-                                                tm->tm_mday), is_interval));
                else
-                   sprintf(inout, "%d",
-                           arg == 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(suf))
-                   str_numth(p_inout, inout, S_TH_TYPE(suf));
-               return strlen(p_inout);
-           }
-           else
-           {
-               int        *field;
-
-               field = (arg == DCH_YYYY) ? &tmfc->year : &tmfc->iyear;
+               {
+                   sscanf(s, "%02d", &out->cc);
+                   s += strspace_len(s) + 2 + 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(suf) || is_next_separator(node))
+               if (S_FM(n->suffix) || is_next_separator(n))
                {
-                   sscanf(inout, "%d", field);
-                   tmfc->yysz = 4;
-                   return strdigits_len(inout) + SKIP_THth(suf);
+                   sscanf(s, "%d", target);
+                   out->yysz = 4;
+                   s += strdigits_len(s) + SKIP_THth(n->suffix);
                }
                else
                {
-                   sscanf(inout, "%04d", field);
-                   tmfc->yysz = 4;
-                   return strspace_len(inout) + 4 + SKIP_THth(suf);
+                   sscanf(s, "%04d", target);
+                   out->yysz = 4;
+                   s += strspace_len(s) + 4 + SKIP_THth(n->suffix);
                }
-           }
-           break;
-       case DCH_YYY:
-       case DCH_IYY:
-           if (is_to_char)
-           {
-               snprintf(buff, sizeof(buff), "%03d",
-                        arg == DCH_YYY ?
-                        ADJUST_YEAR(tm->tm_year, is_interval) :
-                        ADJUST_YEAR(date2isoyear(tm->tm_year,
-                                                 tm->tm_mon, tm->tm_mday),
-                                    is_interval));
-               i = strlen(buff);
-               strcpy(inout, buff + (i - 3));
-               if (S_THth(suf))
-                   str_numth(p_inout, inout, S_TH_TYPE(suf));
-               return strlen(p_inout);
-           }
-           else
-           {
-               int        *field;
-
-               field = (arg == DCH_YYY) ? &tmfc->year : &tmfc->iyear;
+               break;
+           case DCH_YYY:
+           case DCH_IYY:
+               target = (n->key->id == DCH_YYY) ? &out->year : &out->iyear;
 
-               sscanf(inout, "%03d", field);
+               sscanf(s, "%03d", target);
 
                /*
                 * 3-digit year: '100' ... '999' = 1100 ... 1999 '000' ...
                 * '099' = 2000 ... 2099
                 */
-               if (*field >= 100)
-                   *field += 1000;
+               if (*target >= 100)
+                   *target += 1000;
                else
-                   *field += 2000;
-               tmfc->yysz = 3;
-               return strspace_len(inout) + 3 + SKIP_THth(suf);
-           }
-           break;
-       case DCH_YY:
-       case DCH_IY:
-           if (is_to_char)
-           {
-               snprintf(buff, sizeof(buff), "%02d",
-                        arg == DCH_YY ?
-                        ADJUST_YEAR(tm->tm_year, is_interval) :
-                        ADJUST_YEAR(date2isoyear(tm->tm_year,
-                                                 tm->tm_mon, tm->tm_mday),
-                                    is_interval));
-               i = strlen(buff);
-               strcpy(inout, buff + (i - 2));
-               if (S_THth(suf))
-                   str_numth(p_inout, inout, S_TH_TYPE(suf));
-               return strlen(p_inout);
-           }
-           else
-           {
-               int        *field;
-
-               field = (arg == DCH_YY) ? &tmfc->year : &tmfc->iyear;
+                   *target += 2000;
+               out->yysz = 3;
+               s += strspace_len(s) + 3 + SKIP_THth(n->suffix);
+               break;
+           case DCH_YY:
+           case DCH_IY:
+               target = (n->key->id == DCH_YY) ? &out->year : &out->iyear;
 
-               sscanf(inout, "%02d", field);
+               sscanf(s, "%02d", target);
 
                /*
                 * 2-digit year: '00' ... '69'  = 2000 ... 2069 '70' ... '99'
                 * = 1970 ... 1999
                 */
-               if (*field < 70)
-                   *field += 2000;
+               if (*target < 70)
+                   *target += 2000;
                else
-                   *field += 1900;
-               tmfc->yysz = 2;
-               return strspace_len(inout) + 2 + SKIP_THth(suf);
-           }
-           break;
-       case DCH_Y:
-       case DCH_I:
-           if (is_to_char)
-           {
-               snprintf(buff, sizeof(buff), "%1d",
-                        arg == DCH_Y ?
-                        ADJUST_YEAR(tm->tm_year, is_interval) :
-                        ADJUST_YEAR(date2isoyear(tm->tm_year,
-                                                 tm->tm_mon, tm->tm_mday),
-                                    is_interval));
-               i = strlen(buff);
-               strcpy(inout, buff + (i - 1));
-               if (S_THth(suf))
-                   str_numth(p_inout, inout, S_TH_TYPE(suf));
-               return strlen(p_inout);
-           }
-           else
-           {
-               int        *field;
-
-               field = (arg == DCH_Y) ? &tmfc->year : &tmfc->iyear;
+                   *target += 1900;
+               out->yysz = 2;
+               s += strspace_len(s) + 2 + SKIP_THth(n->suffix);
+               break;
+           case DCH_Y:
+           case DCH_I:
+               target = (n->key->id == DCH_Y) ? &out->year : &out->iyear;
 
-               sscanf(inout, "%1d", field);
+               sscanf(s, "%1d", target);
 
                /*
                 * 1-digit year: always +2000
                 */
-               *field += 2000;
-               tmfc->yysz = 1;
-               return strspace_len(inout) + 1 + SKIP_THth(suf);
-           }
-           break;
-       case DCH_RM:
-           if (is_to_char)
-           {
-               if (!tm->tm_mon)
-                   return -1;
-               sprintf(inout, "%*s", S_FM(suf) ? 0 : -4,
-                       rm_months_upper[12 - tm->tm_mon]);
-               return strlen(p_inout);
-           }
-           else
-           {
-               tmfc->mm = 12 - seq_search(inout, rm_months_upper, ALL_UPPER, FULL_SIZ, &len);
+               *target += 2000;
+               out->yysz = 1;
+               s += strspace_len(s) + 1 + 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");
-               return len;
-           }
-           break;
-       case DCH_rm:
-           if (is_to_char)
-           {
-               if (!tm->tm_mon)
-                   return -1;
-               sprintf(inout, "%*s", S_FM(suf) ? 0 : -4,
-                       rm_months_lower[12 - tm->tm_mon]);
-               return strlen(p_inout);
-           }
-           else
-           {
-               tmfc->mm = 12 - seq_search(inout, rm_months_lower, ALL_LOWER, FULL_SIZ, &len);
+               s += len;
+               break;
+           case DCH_rm:
+               out->mm = 12 - seq_search(s, rm_months_lower, ALL_LOWER, FULL_SIZ, &len);
                CHECK_SEQ_SEARCH(len, "rm");
-               return len;
-           }
-           break;
-       case DCH_W:
-           if (is_to_char)
-           {
-               sprintf(inout, "%d", (tm->tm_mday - 1) / 7 + 1);
-               if (S_THth(suf))
-                   str_numth(p_inout, inout, S_TH_TYPE(suf));
-               return strlen(p_inout);
-           }
-           else
-           {
-               sscanf(inout, "%1d", &tmfc->w);
-               return strspace_len(inout) + 1 + SKIP_THth(suf);
-           }
-           break;
-       case DCH_J:
-           if (is_to_char)
-           {
-               sprintf(inout, "%d", date2j(tm->tm_year, tm->tm_mon, tm->tm_mday));
-               if (S_THth(suf))
-                   str_numth(p_inout, inout, S_TH_TYPE(suf));
-               return strlen(p_inout);
-           }
-           else
-           {
-               sscanf(inout, "%d", &tmfc->j);
-               return strdigits_len(inout) + SKIP_THth(suf);
-           }
-           break;
+               s += len;
+               break;
+           case DCH_W:
+               sscanf(s, "%1d", &out->w);
+               s += strspace_len(s) + 1 + SKIP_THth(n->suffix);
+               break;
+           case DCH_J:
+               sscanf(s, "%d", &out->j);
+               s += strdigits_len(s) + SKIP_THth(n->suffix);
+               break;
+       }
    }
-   return -1;
 }
 
 static DCHCacheEntry *
@@ -2914,6 +2697,11 @@ DCH_cache_search(char *str)
    return NULL;
 }
 
+/*
+ * 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)
 {
@@ -2983,7 +2771,7 @@ datetime_to_char_body(TmToChar *tmtc, text *fmt, bool is_interval)
    }
 
    /* The real work is here */
-   DCH_processor(format, result, true, is_interval, (void *) tmtc);
+   DCH_to_char(format, is_interval, tmtc, result);
 
    if (!incache)
        pfree(format);
@@ -3326,6 +3114,13 @@ to_date(PG_FUNCTION_ARGS)
  *
  * Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm
  * and fractional seconds.
+ *
+ * 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' and 'fsec'.
  */
 static void
 do_to_timestamp(text *date_txt, text *fmt,
@@ -3336,11 +3131,10 @@ do_to_timestamp(text *date_txt, text *fmt,
    int         fmt_len,
                year;
 
+   ZERO_tmfc(&tmfc);
    ZERO_tm(tm);
    *fsec = 0;
 
-   ZERO_tmfc(&tmfc);
-
    fmt_len = VARSIZE(fmt) - VARHDRSZ;
 
    if (fmt_len)
@@ -3397,9 +3191,6 @@ do_to_timestamp(text *date_txt, text *fmt,
            format = ent->format;
        }
 
-       /*
-        * Call action for each node in FormatNode tree
-        */
 #ifdef DEBUG_TO_FROM_CHAR
        /* dump_node(format, fmt_len); */
 #endif
@@ -3412,7 +3203,7 @@ do_to_timestamp(text *date_txt, text *fmt,
        memcpy(date_str, VARDATA(date_txt), date_len);
        *(date_str + date_len) = '\0';
 
-       DCH_processor(format, date_str, false, false, (void *) &tmfc);
+       DCH_from_char(format, date_str, &tmfc);
 
        pfree(date_str);
        pfree(fmt_str);