Re: Re: 7.0.3 to_char() (was: [BUGS] Bugs in to_char function) - Mailing list pgsql-patches

From Bruce Momjian
Subject Re: Re: 7.0.3 to_char() (was: [BUGS] Bugs in to_char function)
Date
Msg-id 200010181619.MAA04728@candle.pha.pa.us
Whole thread Raw
In response to Re: Re: 7.0.3 to_char() (was: [BUGS] Bugs in to_char function)  (Karel Zak <zakkr@zf.jcu.cz>)
List pgsql-patches
Unfortunately, it does not apply cleanly to the current CVS tree, and I
can't figure out how to merge your changes.  Current formatting.c
attached.

>
> On Wed, 18 Oct 2000, Tom Lane wrote:
>
> > Karel Zak <zakkr@zf.jcu.cz> writes:
> > > PS. 7.1 contains more changes for to_char/to_timestamp, this fix PM/AM
> > >     only ....
> >
> > Sounds good, but you forgot to attach the patch ...
>
>  Is it a problem? :-)
>
>                 Karel
Content-Description:

[ application/x-gzip is not supported, skipping... ]


--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 853-3000
  +  If your life is a hard drive,     |  830 Blythe Avenue
  +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026
/* -----------------------------------------------------------------------
 * formatting.c
 *
 * $Header: /home/projects/pgsql/cvsroot/pgsql/src/backend/utils/adt/formatting.c,v 1.22 2000/09/25 12:58:47 momjian
Exp$ 
 *
 *
 *     Portions Copyright (c) 1999-2000, PostgreSQL, Inc
 *
 *
 *     TO_CHAR(); TO_TIMESTAMP(); TO_DATE(); TO_NUMBER();
 *
 *     The PostgreSQL routines for a timestamp/int/float/numeric formatting,
 *     inspire with Oracle TO_CHAR() / TO_DATE() / TO_NUMBER() routines.
 *
 *
 *     Cache & Memory:
 *    Routines use (itself) internal cache for format pictures.
 *
 *    The cache uses a static buffers and is persistent across transactions.
 *    If format-picture is bigger than cache buffer, parser is called always.
 *
 *     NOTE for Number version:
 *    All in this version is implemented as keywords ( => not used
 *    suffixes), because a format picture is for *one* item (number)
 *    only. It not is as a timestamp version, where each keyword (can)
 *    has suffix.
 *
 *     NOTE for Timestamp routines:
 *    In this module the POSIX 'struct tm' type is *not* used, but rather
 *    PgSQL type, which has tm_mon based on one (*non* zero) and
 *    year *not* based on 1900, but is used full year number.
 *    Module supports AD / BC / AM / PM.
 *
 *    Supported types for to_char():
 *
 *        Timestamp, Numeric, int4, int8, float4, float8
 *
 *    Supported types for reverse conversion:
 *
 *        Timestamp    - to_timestamp()
 *        Date        - to_date()
 *        Numeric        - to_number()
 *
 *
 *    Karel Zak
 *
 * -----------------------------------------------------------------------
 */

/* ----------
 * UnComment me for DEBUG
 * ----------
 */
/***
#define DEBUG_TO_FROM_CHAR
#define DEBUG_elog_output    NOTICE
***/

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <sys/time.h>
#include <unistd.h>
#include <locale.h>
#include <math.h>
#include <float.h>

#include "postgres.h"
#include "utils/builtins.h"
#include "utils/date.h"
#include "utils/datetime.h"
#include "utils/formatting.h"
#include "utils/int8.h"
#include "utils/pg_locale.h"

/* ----------
 * Routines type
 * ----------
 */
#define DCH_TYPE        1        /* DATE-TIME version    */
#define NUM_TYPE        2        /* NUMBER version    */

/* ----------
 * KeyWord Index (ascii from position 32 (' ') to 126 (~))
 * ----------
 */
#define KeyWord_INDEX_SIZE        ('~' - ' ')
#define KeyWord_INDEX_FILTER(_c)    ((_c) <= ' ' || (_c) >= '~' ? 0 : 1)

/* ----------
 * Maximal length of one node
 * ----------
 */
#define DCH_MAX_ITEM_SIZ        9        /* max julian day        */
#define NUM_MAX_ITEM_SIZ        8        /* roman number (RN has 15 chars)    */

/* ----------
 * More is in float.c
 * ----------
 */
#define MAXFLOATWIDTH    64
#define MAXDOUBLEWIDTH    128

/* ----------
 * External (defined in PgSQL dt.c (timestamp utils))
 * ----------
 */
extern char    *months[],        /* month abbreviation    */
        *days[];        /* full days        */

/* ----------
 * Format parser structs
 * ----------
 */
typedef struct
{
    char    *name;            /* suffix string        */
    int    len,            /* suffix length        */
        id,            /* used in node->suffix */
        type;            /* prefix / postfix            */
} KeySuffix;

typedef struct
{
    char    *name;            /* keyword            */
                    /* action for keyword        */
    int    len,            /* keyword length        */
        (*action) (),
        id;            /* keyword id            */
} KeyWord;

typedef struct
{
    int    type;            /* node type            */
    KeyWord    *key;            /* if node type is KEYWORD    */
    int    character,        /* if node type is CHAR        */
        suffix;            /* keyword suffix        */
} FormatNode;

#define NODE_TYPE_END        1
#define NODE_TYPE_ACTION    2
#define NODE_TYPE_CHAR        3

#define SUFFTYPE_PREFIX        1
#define SUFFTYPE_POSTFIX    2


/* ----------
 * Full months
 * ----------
 */
static char *months_full[] = {
    "January", "February", "March", "April", "May", "June", "July",
    "August", "September", "October", "November", "December", NULL
};

/* ----------
 * AC / DC
 * ----------
 */
#define YEAR_ABS(_y)    (_y < 0 ? -(_y -1) : _y)
#define BC_STR_ORIG " BC"

#define A_D_STR        "A.D."
#define a_d_STR        "a.d."
#define AD_STR        "AD"
#define ad_STR        "ad"

#define B_C_STR        "B.C."
#define b_c_STR        "b.c."
#define BC_STR        "BC"
#define bc_STR        "bc"


/* ----------
 * AM / PM
 * ----------
 */
#define A_M_STR        "A.M."
#define a_m_STR        "a.m."
#define AM_STR        "AM"
#define am_STR        "am"

#define P_M_STR        "P.M."
#define p_m_STR        "p.m."
#define PM_STR        "PM"
#define pm_STR        "pm"


/* ----------
 * Months in roman-numeral
 * (Must be conversely for seq_search (in FROM_CHAR), because
 *    'VIII' must be over 'V')
 * ----------
 */
static char *rm_months_upper[] =
{"XII", "XI", "X", "IX", "VIII", "VII", "VI", "V", "IV", "III", "II", "I", NULL};

static char *rm_months_lower[] =
{"xii", "xi", "x", "ix", "viii", "vii", "vi", "v", "iv", "iii", "ii", "i", NULL};

/* ----------
 * Roman numbers
 * ----------
 */
static char *rm1[] = {"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", NULL};
static char *rm10[] = {"X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC", NULL};
static char *rm100[] = {"C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM", NULL};

/* ----------
 * Ordinal postfixes
 * ----------
 */
static char *numTH[] = {"ST", "ND", "RD", "TH", NULL};
static char *numth[] = {"st", "nd", "rd", "th", NULL};

/* ----------
 * Flags & Options:
 * ----------
 */
#define TO_CHAR        1
#define FROM_CHAR    2

#define ONE_UPPER    1            /* Name */
#define ALL_UPPER    2            /* NAME */
#define ALL_LOWER    3            /* name */

#define FULL_SIZ    0

#define MAX_MON_LEN 3
#define MAX_DY_LEN    3

#define TH_UPPER    1
#define TH_LOWER    2

/* ----------
 * Flags for DCH version
 * ----------
 */
static int        DCH_global_flag = 0;

#define DCH_F_FX    0x01

#define IS_FX        (DCH_global_flag & DCH_F_FX)


/* ----------
 * Number description struct
 * ----------
 */
typedef struct
{
    int    pre,        /* (count) numbers before decimal */
        post,        /* (count) numbers after decimal  */
        lsign,        /* want locales sign          */
        flag,        /* number parametrs          */
        pre_lsign_num,    /* tmp value for lsign          */
        multi,        /* multiplier for 'V'          */
        zero_start,    /* position of first zero      */
        zero_end,    /* position of last zero      */
        need_locale;    /* needs it locale          */
} NUMDesc;

/* ----------
 * Flags for NUMBER version
 * ----------
 */
#define NUM_F_DECIMAL    0x01
#define NUM_F_LDECIMAL    0x02
#define NUM_F_ZERO    0x04
#define NUM_F_BLANK    0x08
#define NUM_F_FILLMODE    0x10
#define NUM_F_LSIGN    0x20
#define NUM_F_BRACKET    0x40
#define NUM_F_MINUS    0x80
#define NUM_F_PLUS    0x100
#define NUM_F_ROMAN    0x200
#define NUM_F_MULTI    0x400

#define NUM_LSIGN_PRE    -1
#define NUM_LSIGN_POST    1
#define NUM_LSIGN_NONE    0

/* ----------
 * Tests
 * ----------
 */
#define IS_DECIMAL(_f)    ((_f)->flag & NUM_F_DECIMAL)
#define IS_LDECIMAL(_f) ((_f)->flag & NUM_F_LDECIMAL)
#define IS_ZERO(_f)    ((_f)->flag & NUM_F_ZERO)
#define IS_BLANK(_f)    ((_f)->flag & NUM_F_BLANK)
#define IS_FILLMODE(_f) ((_f)->flag & NUM_F_FILLMODE)
#define IS_BRACKET(_f)    ((_f)->flag & NUM_F_BRACKET)
#define IS_MINUS(_f)    ((_f)->flag & NUM_F_MINUS)
#define IS_LSIGN(_f)    ((_f)->flag & NUM_F_LSIGN)
#define IS_PLUS(_f)    ((_f)->flag & NUM_F_PLUS)
#define IS_ROMAN(_f)    ((_f)->flag & NUM_F_ROMAN)
#define IS_MULTI(_f)    ((_f)->flag & NUM_F_MULTI)

/* ----------
 * Format picture cache
 *    (cache size:
 *        Number part    = NUM_CACHE_SIZE * NUM_CACHE_FIELDS
 *        Date-time part    = DCH_CACHE_SIZE * DCH_CACHE_FIELDS
 *    )
 * ----------
 */
#define NUM_CACHE_SIZE        64
#define NUM_CACHE_FIELDS    16
#define DCH_CACHE_SIZE        128
#define DCH_CACHE_FIELDS    16

typedef struct
{
    FormatNode    format[DCH_CACHE_SIZE + 1];
    char        str[DCH_CACHE_SIZE + 1];
    int        age;
} DCHCacheEntry;

typedef struct
{
    FormatNode    format[NUM_CACHE_SIZE + 1];
    char        str[NUM_CACHE_SIZE + 1];
    int        age;
    NUMDesc        Num;
} NUMCacheEntry;

/* global cache for --- date/time part */
static DCHCacheEntry DCHCache[DCH_CACHE_FIELDS + 1];

static int    n_DCHCache = 0;        /* number of entries */
static int    DCHCounter = 0;

/* global cache for --- number part */
static NUMCacheEntry NUMCache[NUM_CACHE_FIELDS + 1];

static int    n_NUMCache = 0;        /* number of entries */
static int    NUMCounter = 0;

#define MAX_INT32    (2147483640)

/* ----------
 * For char->date/time conversion
 * ----------
 */
typedef struct {
    int    hh, am, pm, mi, ss, ssss, d, dd, ddd, mm, yyyy, bc, iw, ww, w, cc, q, j;
} TmFromChar;

#define ZERO_tmfc( _X )    \
    do { \
        (_X)->hh= (_X)->am= (_X)->pm= (_X)->mi= (_X)->ss= (_X)->ssss= \
        (_X)->d= (_X)->dd= (_X)->ddd= (_X)->mm= (_X)->yyyy= (_X)->bc= \
        (_X)->iw= (_X)->ww= (_X)->w= (_X)->cc= (_X)->q= (_X)->j= 0; \
    } while(0)

#ifdef DEBUG_TO_FROM_CHAR

#define NOTICE_TMFC \
        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\nyyyy
%d\nbc%d\niw %d\nww %d\nw %d\ncc %d\nq %d\nj %d", \ 
            tmfc->hh, tmfc->am, tmfc->pm, tmfc->mi, tmfc->ss, \
            tmfc->ssss, tmfc->d, tmfc->dd, tmfc->ddd, tmfc->mm, \
            tmfc->yyyy, tmfc->bc, tmfc->iw, tmfc->ww, tmfc->w, \
            tmfc->cc, tmfc->q, tmfc->j);

#define NOTICE_TM \
        elog(DEBUG_elog_output, "TM:\nsec %d\nyear %d\nmin %d\nwday %d\nhour %d\nyday %d\nmday %d\nnisdst %d\nmon
%d\n",\
            tm->tm_sec, tm->tm_year,\
            tm->tm_min, tm->tm_wday, tm->tm_hour, tm->tm_yday,\
            tm->tm_mday, tm->tm_isdst,tm->tm_mon)
#endif

#define ZERO_tm( _X ) \
    do {    \
        (_X)->tm_sec  = (_X)->tm_year = (_X)->tm_min = (_X)->tm_wday = \
        (_X)->tm_hour = (_X)->tm_yday = (_X)->tm_isdst = 0; \
        (_X)->tm_mday = (_X)->tm_mon  = 1; \
    } while(0)

/* ----------
 * Private global-modul definitions
 * ----------
 */
static struct tm _tm, *tm = &_tm;
static TmFromChar _tmfc, *tmfc = &_tmfc;
static char *tzn;

/* ----------
 * Utils
 * ----------
 */
#ifndef MIN
#define MIN(a,b) (((a)<(b)) ? (a) : (b))
#endif
#ifndef MAX
#define MAX(a,b) (((a)>(b)) ? (a) : (b))
#endif

/*****************************************************************************
 *            KeyWords definition & action
 *****************************************************************************/

static int    dch_global(int arg, char *inout, int suf, int flag, FormatNode *node);
static int    dch_time(int arg, char *inout, int suf, int flag, FormatNode *node);
static int    dch_date(int arg, char *inout, int suf, int flag, FormatNode *node);

/* ----------
 * Suffixes:
 * ----------
 */
#define DCH_S_FM    0x01
#define DCH_S_TH    0x02
#define DCH_S_th    0x04
#define DCH_S_SP    0x08

/* ----------
 * Suffix tests
 * ----------
 */
#define S_THth(_s)    (((_s & DCH_S_TH) || (_s & DCH_S_th)) ? 1 : 0)
#define S_TH(_s)    ((_s & DCH_S_TH) ? 1 : 0)
#define S_th(_s)    ((_s & DCH_S_th) ? 1 : 0)
#define S_TH_TYPE(_s)    ((_s & DCH_S_TH) ? TH_UPPER : TH_LOWER)

#define S_FM(_s)    ((_s & DCH_S_FM) ? 1 : 0)
#define S_SP(_s)    ((_s & DCH_S_SP) ? 1 : 0)

/* ----------
 * Suffixes definition for DATE-TIME TO/FROM CHAR
 * ----------
 */
static KeySuffix DCH_suff[] = {
    {"FM", 2, DCH_S_FM, SUFFTYPE_PREFIX},
    {"fm", 2, DCH_S_FM, SUFFTYPE_PREFIX},
    {"TH", 2, DCH_S_TH, SUFFTYPE_POSTFIX},
    {"th", 2, DCH_S_th, SUFFTYPE_POSTFIX},
    {"SP", 2, DCH_S_SP, SUFFTYPE_POSTFIX},
    /* last */
    {NULL, 0, 0, 0}
};

/* ----------
 * Format-pictures (KeyWord).
 *
 * The KeyWord field; alphabetic sorted, *BUT* strings alike is sorted
 *          complicated -to-> easy:
 *
 *    (example: "DDD","DD","Day","D" )
 *
 * (this specific sort needs the algorithm for sequential search for strings,
 * which not has exact end; -> How keyword is in "HH12blabla" ? - "HH"
 * or "HH12"? You must first try "HH12", because "HH" is in string, but
 * it is not good.
 *
 * (!)
 *      Position for the keyword is simular as position in the enum DCH/NUM_poz
 * (!)
 *
 * For fast search is used the 'int index[]', index is ascii table from position
 * 32 (' ') to 126 (~), in this index is DCH_ / NUM_ enums for each ASCII
 * position or -1 if char is not used in the KeyWord. Search example for
 * string "MM":
 *    1)    see in index to index['M' - 32],
 *    2)    take keywords position (enum DCH_MM) from index
 *    3)    run sequential search in keywords[] from this position
 *
 * ----------
 */

typedef enum
{
    DCH_A_D,
    DCH_A_M,
    DCH_AD,
    DCH_AM,
    DCH_B_C,
    DCH_BC,
    DCH_CC,
    DCH_DAY,
    DCH_DDD,
    DCH_DD,
    DCH_DY,
    DCH_Day,
    DCH_Dy,
    DCH_D,
    DCH_FX,                        /* global suffix */
    DCH_HH24,
    DCH_HH12,
    DCH_HH,
    DCH_IW,
    DCH_J,
    DCH_MI,
    DCH_MM,
    DCH_MONTH,
    DCH_MON,
    DCH_Month,
    DCH_Mon,
    DCH_P_M,
    DCH_PM,
    DCH_Q,
    DCH_RM,
    DCH_SSSS,
    DCH_SS,
    DCH_TZ,
    DCH_WW,
    DCH_W,
    DCH_Y_YYY,
    DCH_YYYY,
    DCH_YYY,
    DCH_YY,
    DCH_Y,
    DCH_a_d,
    DCH_a_m,
    DCH_ad,
    DCH_am,
    DCH_b_c,
    DCH_bc,
    DCH_cc,
    DCH_day,
    DCH_ddd,
    DCH_dd,
    DCH_dy,
    DCH_d,
    DCH_fx,
    DCH_hh24,
    DCH_hh12,
    DCH_hh,
    DCH_iw,
    DCH_j,
    DCH_mi,
    DCH_mm,
    DCH_month,
    DCH_mon,
    DCH_p_m,
    DCH_pm,
    DCH_q,
    DCH_rm,
    DCH_ssss,
    DCH_ss,
    DCH_tz,
    DCH_ww,
    DCH_w,
    DCH_y_yyy,
    DCH_yyyy,
    DCH_yyy,
    DCH_yy,
    DCH_y,

    /* last */
    _DCH_last_
} DCH_poz;

typedef enum
{
    NUM_COMMA,
    NUM_DEC,
    NUM_0,
    NUM_9,
    NUM_B,
    NUM_C,
    NUM_D,
    NUM_E,
    NUM_FM,
    NUM_G,
    NUM_L,
    NUM_MI,
    NUM_PL,
    NUM_PR,
    NUM_RN,
    NUM_SG,
    NUM_SP,
    NUM_S,
    NUM_TH,
    NUM_V,
    NUM_b,
    NUM_c,
    NUM_d,
    NUM_e,
    NUM_fm,
    NUM_g,
    NUM_l,
    NUM_mi,
    NUM_pl,
    NUM_pr,
    NUM_rn,
    NUM_sg,
    NUM_sp,
    NUM_s,
    NUM_th,
    NUM_v,

    /* last */
    _NUM_last_
} NUM_poz;

/* ----------
 * KeyWords for DATE-TIME version
 * ----------
 */
static KeyWord DCH_keywords[] = {
/*    keyword,len,func.type             is in Index */
    {"A.D.", 4, dch_date, DCH_A_D},        /* A */
    {"A.M.", 4, dch_time, DCH_A_M},
    {"AD", 2, dch_date, DCH_AD},
    {"AM", 2, dch_time, DCH_AM},
    {"B.C.", 4, dch_date, DCH_B_C},        /* B */
    {"BC", 2, dch_date, DCH_BC},
    {"CC", 2, dch_date, DCH_CC},        /* C */
    {"DAY", 3, dch_date, DCH_DAY},        /* D */
    {"DDD", 3, dch_date, DCH_DDD},
    {"DD", 2, dch_date, DCH_DD},
    {"DY", 2, dch_date, DCH_DY},
    {"Day", 3, dch_date, DCH_Day},
    {"Dy", 2, dch_date, DCH_Dy},
    {"D", 1, dch_date, DCH_D},
    {"FX", 2, dch_global, DCH_FX},        /* F */
    {"HH24", 4, dch_time, DCH_HH24},    /* H */
    {"HH12", 4, dch_time, DCH_HH12},
    {"HH", 2, dch_time, DCH_HH},
    {"IW", 2, dch_date, DCH_IW},        /* I */
    {"J", 1, dch_date, DCH_J},        /* J */
    {"MI", 2, dch_time, DCH_MI},
    {"MM", 2, dch_date, DCH_MM},
    {"MONTH", 5, dch_date, DCH_MONTH},
    {"MON", 3, dch_date, DCH_MON},
    {"Month", 5, dch_date, DCH_Month},
    {"Mon", 3, dch_date, DCH_Mon},
    {"P.M.", 4, dch_time, DCH_P_M},        /* P */
    {"PM", 2, dch_time, DCH_PM},
    {"Q", 1, dch_date, DCH_Q},        /* Q */
    {"RM", 2, dch_date, DCH_RM},        /* R */
    {"SSSS", 4, dch_time, DCH_SSSS},    /* S */
    {"SS", 2, dch_time, DCH_SS},
    {"TZ", 2, dch_time, DCH_TZ},        /* T */
    {"WW", 2, dch_date, DCH_WW},        /* W */
    {"W", 1, dch_date, DCH_W},
    {"Y,YYY", 5, dch_date, DCH_Y_YYY},    /* Y */
    {"YYYY", 4, dch_date, DCH_YYYY},
    {"YYY", 3, dch_date, DCH_YYY},
    {"YY", 2, dch_date, DCH_YY},
    {"Y", 1, dch_date, DCH_Y},
    {"a.d.", 4, dch_date, DCH_a_d},        /* a */
    {"a.m.", 4, dch_time, DCH_a_m},
    {"ad", 2, dch_date, DCH_ad},
    {"am", 2, dch_time, DCH_am},
    {"b.c.", 4, dch_date, DCH_b_c},        /* b */
    {"bc", 2, dch_date, DCH_bc},
    {"cc", 2, dch_date, DCH_CC},        /* c */
    {"day", 3, dch_date, DCH_day},        /* d */
    {"ddd", 3, dch_date, DCH_DDD},
    {"dd", 2, dch_date, DCH_DD},
    {"dy", 2, dch_date, DCH_dy},
    {"d", 1, dch_date, DCH_D},
    {"fx", 2, dch_global, DCH_FX},        /* f */
    {"hh24", 4, dch_time, DCH_HH24},    /* h */
    {"hh12", 4, dch_time, DCH_HH12},
    {"hh", 2, dch_time, DCH_HH},
    {"iw", 2, dch_date, DCH_IW},        /* i */
    {"j", 1, dch_time, DCH_J},        /* j */
    {"mi", 2, dch_time, DCH_MI},        /* m */
    {"mm", 2, dch_date, DCH_MM},
    {"month", 5, dch_date, DCH_month},
    {"mon", 3, dch_date, DCH_mon},
    {"p.m.", 4, dch_time, DCH_p_m},        /* p */
    {"pm", 2, dch_time, DCH_pm},
    {"q", 1, dch_date, DCH_Q},        /* q */
    {"rm", 2, dch_date, DCH_rm},        /* r */
    {"ssss", 4, dch_time, DCH_SSSS},    /* s */
    {"ss", 2, dch_time, DCH_SS},
    {"tz", 2, dch_time, DCH_tz},        /* t */
    {"ww", 2, dch_date, DCH_WW},        /* w */
    {"w", 1, dch_date, DCH_W},
    {"y,yyy", 5, dch_date, DCH_Y_YYY},    /* y */
    {"yyyy", 4, dch_date, DCH_YYYY},
    {"yyy", 3, dch_date, DCH_YYY},
    {"yy", 2, dch_date, DCH_YY},
    {"y", 1, dch_date, DCH_Y},
/* last */
{NULL, 0, NULL, 0}};

/* ----------
 * KeyWords for NUMBER version
 * ----------
 */
static 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}};


/* ----------
 * KeyWords index for DATE-TIME version
 * ----------
 */
static int    DCH_index[KeyWord_INDEX_SIZE] = {
/*
0    1    2    3    4    5    6    7    8    9
*/
    /*---- first 0..31 chars are skiped ----*/

    -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, DCH_A_D, DCH_B_C, DCH_CC, DCH_DAY, -1,
    DCH_FX, -1, DCH_HH24, DCH_IW, DCH_J, -1, -1, DCH_MI, -1, -1,
    DCH_P_M, DCH_Q, DCH_RM, DCH_SSSS, DCH_TZ, -1, -1, DCH_WW, -1, DCH_Y_YYY,
    -1, -1, -1, -1, -1, -1, -1, DCH_a_d, DCH_b_c, DCH_cc,
    DCH_day, -1, DCH_fx, -1, DCH_hh24, DCH_iw, DCH_j, -1, -1, DCH_mi,
    -1, -1, DCH_p_m, DCH_q, DCH_rm, DCH_ssss, DCH_tz, -1, -1, DCH_ww,
    -1, DCH_y_yyy, -1, -1, -1, -1

    /*---- chars over 126 are skiped ----*/
};

/* ----------
 * KeyWords index for NUMBER version
 * ----------
 */
static int    NUM_index[KeyWord_INDEX_SIZE] = {
/*
0    1    2    3    4    5    6    7    8    9
*/
    /*---- first 0..31 chars are skiped ----*/

    -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, NUM_COMMA, -1, NUM_DEC, -1, NUM_0, -1,
    -1, -1, -1, -1, -1, -1, -1, NUM_9, -1, -1,
    -1, -1, -1, -1, -1, -1, NUM_B, NUM_C, NUM_D, NUM_E,
    NUM_FM, NUM_G, -1, -1, -1, -1, NUM_L, NUM_MI, -1, -1,
    NUM_PL, -1, NUM_RN, NUM_SG, NUM_TH, -1, NUM_V, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, NUM_b, NUM_c,
    NUM_d, NUM_e, NUM_fm, NUM_g, -1, -1, -1, -1, NUM_l, NUM_mi,
    -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 ----*/
};

/* ----------
 * Number processor struct
 * ----------
 */
typedef struct NUMProc
{
    int        type;            /* FROM_CHAR (TO_NUMBER) or TO_CHAR */

    NUMDesc        *Num;            /* number description        */

    int        sign,            /* '-' or '+'            */
            sign_wrote,        /* was sign write        */
            sign_pos,        /* pre number sign position */
            num_count,        /* number of write digits    */
            num_in,            /* is inside number        */
            num_curr,        /* current position in number    */
            num_pre,        /* space before first number    */

            read_dec,        /* to_number - was read dec. point    */
            read_post;        /* to_number - number of dec. digit */

    char        *number,        /* string with number    */
            *number_p,        /* pointer to current number pozition */
            *inout,            /* in / out buffer    */
            *inout_p,        /* pointer to current inout pozition */
            *last_relevant,        /* last relevant number after decimal point */

            *L_negative_sign,/* Locale */
            *L_positive_sign,
            *decimal,
            *L_thousands_sep,
            *L_currency_symbol;
} NUMProc;


/* ----------
 * Functions
 * ----------
 */
static KeyWord *index_seq_search(char *str, KeyWord *kw, int *index);
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, KeyWord *kw,
             KeySuffix *suf, int *index, int ver, NUMDesc *Num);
static char *DCH_processor(FormatNode *node, char *inout, int flag);

#ifdef DEBUG_TO_FROM_CHAR
static void dump_index(KeyWord *k, int *index);
static void dump_node(FormatNode *node, int max);

#endif

static char *get_th(char *num, int type);
static char *str_numth(char *dest, char *num, int type);
static int    int4len(int4 num);
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 int    dch_global(int arg, char *inout, int suf, int flag, FormatNode *node);
static int    dch_time(int arg, char *inout, int suf, int flag, FormatNode *node);
static int    dch_date(int arg, char *inout, int suf, int flag, FormatNode *node);
static char *fill_str(char *str, int c, int max);
static FormatNode *NUM_cache(int len, NUMDesc *Num, char *pars_str, int *flag);
static char *int_to_roman(int number);
static void NUM_prepare_locale(NUMProc *Np);
static char *get_last_relevant_decnum(char *num);
static void NUM_numpart_from_char(NUMProc *Np, int id, int plen);
static void NUM_numpart_to_char(NUMProc *Np, int id);
static char *NUM_processor(FormatNode *node, NUMDesc *Num, char *inout, char *number,
              int plen, int sign, int type);
static DCHCacheEntry *DCH_cache_search(char *str);
static DCHCacheEntry *DCH_cache_getnew(char *str);
static NUMCacheEntry *NUM_cache_search(char *str);
static NUMCacheEntry *NUM_cache_getnew(char *str);


/* ----------
 * Fast sequential search, use index for data selection which
 * go to seq. cycle (it is very fast for non-wanted strings)
 * (can't be used binary search in format parsing)
 * ----------
 */
static KeyWord *
index_seq_search(char *str, KeyWord *kw, int *index)
{
    int            poz;

    if (!KeyWord_INDEX_FILTER(*str))
        return (KeyWord *) NULL;

    if ((poz = *(index + (*str - ' '))) > -1)
    {

        KeyWord    *k = kw + poz;

        do
        {
            if (!strncmp(str, k->name, k->len))
                return k;
            k++;
            if (!k->name)
                return (KeyWord *) NULL;
        } while (*str == *k->name);
    }
    return (KeyWord *) NULL;
}

static KeySuffix *
suff_search(char *str, KeySuffix *suf, int type)
{
    KeySuffix  *s;

    for (s = suf; s->name != NULL; s++)
    {
        if (s->type != type)
            continue;

        if (!strncmp(str, s->name, s->len))
            return s;
    }
    return (KeySuffix *) NULL;
}

/* ----------
 * Prepare NUMDesc (number description struct) via FormatNode struct
 * ----------
 */
static void
NUMDesc_prepare(NUMDesc *num, FormatNode *n)
{

    if (n->type != NODE_TYPE_ACTION)
        return;

    switch (n->key->id)
    {

        case NUM_9:
            if (IS_BRACKET(num))
                elog(ERROR, "to_char/to_number(): '9' must be ahead of 'PR'.");

            if (IS_MULTI(num))
            {
                ++num->multi;
                break;
            }
            if (IS_DECIMAL(num))
                ++num->post;
            else
                ++num->pre;
            break;

        case NUM_0:
            if (IS_BRACKET(num))
                elog(ERROR, "to_char/to_number(): '0' must be ahead of 'PR'.");

            if (!IS_ZERO(num) && !IS_DECIMAL(num))
            {
                num->flag |= NUM_F_ZERO;
                num->zero_start = num->pre + 1;
            }
            if (!IS_DECIMAL(num))
                ++num->pre;
            else
                ++num->post;

            num->zero_end = num->pre + num->post;
            break;

        case NUM_B:
            if (num->pre == 0 && num->post == 0 && (!IS_ZERO(num)))
                num->flag |= NUM_F_BLANK;
            break;

        case NUM_D:
            num->flag |= NUM_F_LDECIMAL;
            num->need_locale = TRUE;
        case NUM_DEC:
            if (IS_DECIMAL(num))
                elog(ERROR, "to_char/to_number(): not unique decimal poit.");
            if (IS_MULTI(num))
                elog(ERROR, "to_char/to_number(): can't use 'V' and decimal poin together.");
            num->flag |= NUM_F_DECIMAL;
            break;

        case NUM_FM:
            num->flag |= NUM_F_FILLMODE;
            break;

        case NUM_S:
            if (IS_LSIGN(num))
                elog(ERROR, "to_char/to_number(): not unique 'S'.");

            if (IS_PLUS(num) || IS_MINUS(num) || IS_BRACKET(num))
                elog(ERROR, "to_char/to_number(): can't use 'S' and 'PL'/'MI'/'SG'/'PR' together.");

            if (!IS_DECIMAL(num))
            {
                num->lsign = NUM_LSIGN_PRE;
                num->pre_lsign_num = num->pre;
                num->need_locale = TRUE;
                num->flag |= NUM_F_LSIGN;

            }
            else if (num->lsign == NUM_LSIGN_NONE)
            {
                num->lsign = NUM_LSIGN_POST;
                num->need_locale = TRUE;
                num->flag |= NUM_F_LSIGN;
            }
            break;

        case NUM_MI:
            if (IS_LSIGN(num))
                elog(ERROR, "to_char/to_number(): can't use 'S' and 'MI' together.");

            num->flag |= NUM_F_MINUS;
            break;

        case NUM_PL:
            if (IS_LSIGN(num))
                elog(ERROR, "to_char/to_number(): can't use 'S' and 'PL' together.");

            num->flag |= NUM_F_PLUS;
            break;

        case NUM_SG:
            if (IS_LSIGN(num))
                elog(ERROR, "to_char/to_number(): can't use 'S' and 'SG' together.");

            num->flag |= NUM_F_MINUS;
            num->flag |= NUM_F_PLUS;
            break;

        case NUM_PR:
            if (IS_LSIGN(num) || IS_PLUS(num) || IS_MINUS(num))
                elog(ERROR, "to_char/to_number(): can't use 'PR' and 'S'/'PL'/'MI'/'SG' together.");
            num->flag |= NUM_F_BRACKET;
            break;

        case NUM_rn:
        case NUM_RN:
            num->flag |= NUM_F_ROMAN;
            break;

        case NUM_L:
        case NUM_G:
            num->need_locale = TRUE;
            break;

        case NUM_V:
            if (IS_DECIMAL(num))
                elog(ERROR, "to_char/to_number(): can't use 'V' and decimal poin together.");
            num->flag |= NUM_F_MULTI;
            break;

        case NUM_E:
            elog(ERROR, "to_char/to_number(): 'E' is not supported.");
    }

    return;
}

/* ----------
 * Format parser, search small keywords and keyword's suffixes, and make
 * format-node tree.
 *
 * for DATE-TIME & NUMBER version
 * ----------
 */
static void
parse_format(FormatNode *node, char *str, KeyWord *kw,
             KeySuffix *suf, int *index, int ver, NUMDesc *Num)
{
    KeySuffix    *s;
    FormatNode    *n;
    int        node_set = 0,
            suffix,
            last = 0;

#ifdef DEBUG_TO_FROM_CHAR
    elog(DEBUG_elog_output, "to_char/number(): run parser.");
#endif

    n = node;

    while (*str)
    {
        suffix = 0;

        /* ----------
         * Prefix
         * ----------
         */
        if (ver == DCH_TYPE && (s = suff_search(str, suf, SUFFTYPE_PREFIX)) != NULL)
        {
            suffix |= s->id;
            if (s->len)
                str += s->len;
        }

        /* ----------
         * Keyword
         * ----------
         */
        if (*str && (n->key = index_seq_search(str, kw, index)) != NULL)
        {

            n->type = NODE_TYPE_ACTION;
            n->suffix = 0;
            node_set = 1;
            if (n->key->len)
                str += n->key->len;

            /* ----------
             * NUM version: Prepare global NUMDesc struct
             * ----------
             */
            if (ver == NUM_TYPE)
                NUMDesc_prepare(Num, n);

            /* ----------
             * Postfix
             * ----------
             */
            if (ver == DCH_TYPE && *str && (s = suff_search(str, suf, SUFFTYPE_POSTFIX)) != NULL)
            {
                suffix |= s->id;
                if (s->len)
                    str += s->len;
            }

        }
        else if (*str)
        {

            /* ----------
             * Special characters '\' and '"'
             * ----------
             */
            if (*str == '"' && last != '\\')
            {

                int            x = 0;

                while (*(++str))
                {
                    if (*str == '"' && x != '\\')
                    {
                        str++;
                        break;
                    }
                    else if (*str == '\\' && x != '\\')
                    {
                        x = '\\';
                        continue;
                    }
                    n->type = NODE_TYPE_CHAR;
                    n->character = *str;
                    n->key = (KeyWord *) NULL;
                    n->suffix = 0;
                    ++n;
                    x = *str;
                }
                node_set = 0;
                suffix = 0;
                last = 0;

            }
            else if (*str && *str == '\\' && last != '\\' && *(str + 1) == '"')
            {
                last = *str;
                str++;

            }
            else if (*str)
            {
                n->type = NODE_TYPE_CHAR;
                n->character = *str;
                n->key = (KeyWord *) NULL;
                node_set = 1;
                last = 0;
                str++;
            }

        }

        /* end */
        if (node_set)
        {
            if (n->type == NODE_TYPE_ACTION)
                n->suffix = suffix;
            ++n;

            n->suffix = 0;
            node_set = 0;
        }

    }

    n->type = NODE_TYPE_END;
    n->suffix = 0;
    return;
}

/* ----------
 * Call keyword's function for each of (action) node in format-node tree
 * ----------
 */
static char *
DCH_processor(FormatNode *node, char *inout, int flag)
{
    FormatNode *n;
    char       *s;


    /* ----------
     * Zeroing global flags
     * ----------
     */
    DCH_global_flag = 0;

    for (n = node, s = inout; n->type != NODE_TYPE_END; n++)
    {
        if (n->type == NODE_TYPE_ACTION)
        {
            int    len;

            /* ----------
             * Call node action function
             * ----------
             */
            len = n->key->action(n->key->id, s, n->suffix, flag, n);
            if (len > 0)
                s += len;
            else if (len == -1)
                continue;

        }
        else
        {

            /* ----------
             * Remove to output char from input in TO_CHAR
             * ----------
             */
            if (flag == TO_CHAR)
                *s = n->character;

            else
            {
                /* ----------
                 * Skip blank space in FROM_CHAR's input
                 * ----------
                 */
                if (isspace(n->character) && IS_FX == 0)
                {
                    while (*s != '\0' && isspace((int) *(s + 1)))
                        ++s;
                }
            }
        }

        ++s;                    /* ! */

    }

    if (flag == TO_CHAR)
        *s = '\0';
    return inout;
}


/* ----------
 * DEBUG: Dump the FormatNode Tree (debug)
 * ----------
 */
#ifdef DEBUG_TO_FROM_CHAR

#define DUMP_THth(_suf) (S_TH(_suf) ? "TH" : (S_th(_suf) ? "th" : " "))
#define DUMP_FM(_suf)    (S_FM(_suf) ? "FM" : " ")

static void
dump_node(FormatNode *node, int max)
{
    FormatNode *n;
    int            a;

    elog(DEBUG_elog_output, "to_from-char(): DUMP FORMAT");

    for (a = 0, n = node; a <= max; n++, a++)
    {
        if (n->type == NODE_TYPE_ACTION)
            elog(DEBUG_elog_output, "%d:\t NODE_TYPE_ACTION '%s'\t(%s,%s)",
              a, n->key->name, DUMP_THth(n->suffix), DUMP_FM(n->suffix));
        else if (n->type == NODE_TYPE_CHAR)
            elog(DEBUG_elog_output, "%d:\t NODE_TYPE_CHAR '%c'", a, n->character);
        else if (n->type == NODE_TYPE_END)
        {
            elog(DEBUG_elog_output, "%d:\t NODE_TYPE_END", a);
            return;
        }
        else
            elog(DEBUG_elog_output, "%d:\t UnKnown NODE !!!", a);

    }
}

#endif

/*****************************************************************************
 *            Private utils
 *****************************************************************************/

/* ----------
 * Return ST/ND/RD/TH for simple (1..9) numbers
 * type --> 0 upper, 1 lower
 * ----------
 */
static char *
get_th(char *num, int type)
{
    int    len = strlen(num),
        last, seclast;

    last = *(num + (len - 1));
    if (!isdigit((unsigned char) last))
        elog(ERROR, "get_th: '%s' is not number.", num);

    /*
     * All "teens" (<x>1[0-9]) get 'TH/th',
     * while <x>[02-9][123] still get 'ST/st', 'ND/nd', 'RD/rd', respectively
     */
    if ((len > 1) && ((seclast = num[len-2]) == '1'))
        last = 0;

    switch (last)
    {
        case '1':
            if (type == TH_UPPER)
                return numTH[0];
            return numth[0];
        case '2':
            if (type == TH_UPPER)
                return numTH[1];
            return numth[1];
        case '3':
            if (type == TH_UPPER)
                return numTH[2];
            return numth[2];
        default:
            if (type == TH_UPPER)
                return numTH[3];
            return numth[3];
    }
    return NULL;
}

/* ----------
 * Convert string-number to ordinal string-number
 * type --> 0 upper, 1 lower
 * ----------
 */
static char *
str_numth(char *dest, char *num, int type)
{
    sprintf(dest, "%s%s", num, get_th(num, type));
    return dest;
}

/* ----------
 * Return length of integer writed in string
 * ----------
 */
static int
int4len(int4 num)
{
    char        b[16];

    return sprintf(b, "%d", num);
}

/* ----------
 * Convert string to upper-string
 * ----------
 */
static char *
str_toupper(char *buff)
{
    char       *p_buff = buff;

    while (*p_buff)
    {
        *p_buff = toupper((unsigned char) *p_buff);
        ++p_buff;
    }
    return buff;
}

/* ----------
 * Convert string to lower-string
 * ----------
 */
static char *
str_tolower(char *buff)
{
    char       *p_buff = buff;

    while (*p_buff)
    {
        *p_buff = tolower((unsigned char) *p_buff);
        ++p_buff;
    }
    return buff;
}

/* ----------
 * 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 || ALL_UPPER)
        *name = toupper((unsigned char) *name);
    else if (type == ALL_LOWER)
        *name = 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 = tolower((unsigned char) *n);
                else if (type == ALL_UPPER)
                    *n = 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;
}


#ifdef DEBUG_TO_FROM_CHAR
/* -----------
 * DEBUG: Call for debug and for index checking; (Show ASCII char
 * and defined keyword for each used position
 * ----------
 */
static void
dump_index(KeyWord *k, int *index)
{
    int    i,
        count = 0,
        free_i = 0;

    elog(DEBUG_elog_output, "TO-FROM_CHAR: Dump KeyWord Index:");

    for (i = 0; i < KeyWord_INDEX_SIZE; i++)
    {
        if (index[i] != -1)
        {
            elog(DEBUG_elog_output, "\t%c: %s, ", i + 32, k[index[i]].name);
            count++;
        }
        else
        {
            free_i++;
            elog(DEBUG_elog_output, "\t(%d) %c %d", i, i + 32, index[i]);
        }
    }
    elog(DEBUG_elog_output, "\n\t\tUsed positions: %d,\n\t\tFree positions: %d",
         count, free_i);
}

#endif

/* ----------
 * Skip TM / th in FROM_CHAR
 * ----------
 */
#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, int flag, FormatNode *node)
{
    switch (arg)
    {
        case DCH_FX:
        DCH_global_flag |= DCH_F_FX;
        break;
    }
    return -1;
}

#define AMPM_ERROR    elog(ERROR, "to_timestamp(): bad AM/PM string")

/* ----------
 * Master function of TIME for:
 *              TO_CHAR    - write (inout) formated string
 *              FROM_CHAR - scan (inout) string by course of FormatNode
 * ----------
 */
static int
dch_time(int arg, char *inout, int suf, int flag, FormatNode *node)
{
    char       *p_inout = inout;

    switch (arg)
    {
        case DCH_A_M:
        case DCH_P_M:
            if (flag == TO_CHAR)
            {
                strcpy(inout, ((tm->tm_hour > 11
                    && tm->tm_hour < 24) ? P_M_STR : A_M_STR));
                return 3;
            }
            else if (flag == FROM_CHAR)
            {
                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 3;
            }
            break;
        case DCH_AM:
        case DCH_PM:
            if (flag == TO_CHAR)
            {
                strcpy(inout, ((tm->tm_hour > 11
                    && tm->tm_hour < 24) ? PM_STR : AM_STR));
                return 1;
            }
            else if (flag == FROM_CHAR)
            {
                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 1;
            }
            break;
        case DCH_a_m:
        case DCH_p_m:
            if (flag == TO_CHAR)
            {
                strcpy(inout, ((tm->tm_hour > 11
                    && tm->tm_hour < 24) ? p_m_STR : a_m_STR));
                return 3;
            }
            else if (flag == FROM_CHAR)
            {
                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 3;
            }
            break;
        case DCH_am:
        case DCH_pm:
            if (flag == TO_CHAR)
            {
                strcpy(inout, ((tm->tm_hour > 11
                    && tm->tm_hour < 24) ? pm_STR : am_STR));
                return 1;
            }
            else if (flag == FROM_CHAR)
            {
                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 1;
            }
            break;
        case DCH_HH:
        case DCH_HH12:
            if (flag == TO_CHAR)
            {
                sprintf(inout, "%0*d", S_FM(suf) ? 0 : 2,
                        tm->tm_hour == 0 ? 12 :
                      tm->tm_hour < 13 ? tm->tm_hour : tm->tm_hour - 12);
                if (S_THth(suf))
                    str_numth(p_inout, inout, 0);
                if (S_FM(suf) || S_THth(suf))
                    return strlen(p_inout) - 1;
                else
                    return 1;

            }
            else if (flag == FROM_CHAR)
            {
                if (S_FM(suf))
                {
                    sscanf(inout, "%d", &tmfc->hh);
                    return int4len((int4) tmfc->hh) - 1 + SKIP_THth(suf);
                }
                else
                {
                    sscanf(inout, "%02d", &tmfc->hh);
                    return 1 + SKIP_THth(suf);
                }
            }
            break;
        case DCH_HH24:
            if (flag == 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));
                if (S_FM(suf) || S_THth(suf))
                    return strlen(p_inout) - 1;
                else
                    return 1;

            }
            else if (flag == FROM_CHAR)
            {
                if (S_FM(suf))
                {
                    sscanf(inout, "%d", &tmfc->hh);
                    return int4len((int4) tmfc->hh) - 1 + SKIP_THth(suf);
                }
                else
                {
                    sscanf(inout, "%02d", &tmfc->hh);
                    return 1 + SKIP_THth(suf);
                }
            }
            break;
        case DCH_MI:
            if (flag == 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));
                if (S_FM(suf) || S_THth(suf))
                    return strlen(p_inout) - 1;
                else
                    return 1;

            }
            else if (flag == FROM_CHAR)
            {
                if (S_FM(suf))
                {
                    sscanf(inout, "%d", &tmfc->mi);
                    return int4len((int4) tmfc->mi) - 1 + SKIP_THth(suf);
                }
                else
                {
                    sscanf(inout, "%02d", &tmfc->mi);
                    return 1 + SKIP_THth(suf);
                }
            }
            break;
        case DCH_SS:
            if (flag == 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));
                if (S_FM(suf) || S_THth(suf))
                    return strlen(p_inout) - 1;
                else
                    return 1;

            }
            else if (flag == FROM_CHAR)
            {
                if (S_FM(suf))
                {
                    sscanf(inout, "%d", &tmfc->ss);
                    return int4len((int4) tmfc->ss) - 1 + SKIP_THth(suf);
                }
                else
                {
                    sscanf(inout, "%02d", &tmfc->ss);
                    return 1 + SKIP_THth(suf);
                }
            }
            break;
        case DCH_SSSS:
            if (flag == TO_CHAR)
            {
                sprintf(inout, "%d", tm->tm_hour * 3600 +
                        tm->tm_min * 60 +
                        tm->tm_sec);
                if (S_THth(suf))
                    str_numth(p_inout, inout, S_TH_TYPE(suf));
                return strlen(p_inout) - 1;
            }
            else if (flag == FROM_CHAR)
            {
                sscanf(inout, "%d", &tmfc->ssss);
                return int4len((int4) tmfc->ssss) - 1 + SKIP_THth(suf);
            }
            break;
        case DCH_tz:
        case DCH_TZ:
            if (flag == TO_CHAR && tzn)
            {
                int siz = strlen(tzn);

                if (arg == DCH_TZ)
                    strcpy(inout, tzn);
                else
                {
                    char *p = palloc(siz);

                    strcpy(p, tzn);
                    strcpy(inout, str_tolower(p));
                    pfree(p);
                }
                return siz - 1;
            }
            else if (flag == FROM_CHAR)
            {
                elog(ERROR, "to_timestamp(): TZ/tz not supported.");
            }
    }
    return -1;
}

#define CHECK_SEQ_SEARCH(_l, _s) \
do { \
    if (_l <= 0) {                            \
        elog(ERROR, "to_timestamp(): bad value for %s", _s);    \
    }                                \
} while (0)


/* ----------
 * Master of DATE for:
 *          TO_CHAR - write (inout) formated string
 *          FROM_CHAR - scan (inout) string by course of FormatNode
 * ----------
 */
static int
dch_date(int arg, char *inout, int suf, int flag, FormatNode *node)
{
    char    buff[DCH_CACHE_SIZE],
        *p_inout;
    int    i,
        len;

    p_inout = inout;

    /* ----------
     * In the FROM-char is not 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 (flag == FROM_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");
            if (S_FM(suf))
                return len - 1;
            else
                return 8;

        }
        else if (arg == DCH_MON || arg == DCH_Mon || arg == DCH_mon)
        {
            tmfc->mm = seq_search(inout, months, ONE_UPPER, MAX_MON_LEN, &len) +1;
            CHECK_SEQ_SEARCH(len, "MON/Mon/mon");
            return 2;
        }
        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");
            if (S_FM(suf))
                return len - 1;
            else
                return 8;

        }
        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 2;
        }
    }

    switch (arg)
    {

        case DCH_A_D:
        case DCH_B_C:
            if (flag == TO_CHAR)
            {
                strcpy(inout, (tm->tm_year < 0 ? B_C_STR : A_D_STR));
                return 3;

            }
            else if (flag == FROM_CHAR)
            {
                if (strncmp(inout, B_C_STR, 4) == 0)
                    tmfc->bc = TRUE;
                return 3;
            }
            break;
        case DCH_AD:
        case DCH_BC:
            if (flag == TO_CHAR)
            {
                strcpy(inout, (tm->tm_year < 0 ? BC_STR : AD_STR));
                return 1;

            }
            else if (flag == FROM_CHAR)
            {
                if (strncmp(inout, BC_STR, 2) == 0)
                    tmfc->bc = TRUE;
                return 1;
            }
            break;
        case DCH_a_d:
        case DCH_b_c:
            if (flag == TO_CHAR)
            {
                strcpy(inout, (tm->tm_year < 0 ? b_c_STR : a_d_STR));
                return 3;

            }
            else if (flag == FROM_CHAR)
            {
                if (strncmp(inout, b_c_STR, 4) == 0)
                    tmfc->bc = TRUE;
                return 3;
            }
            break;
        case DCH_ad:
        case DCH_bc:
            if (flag == TO_CHAR)
            {
                strcpy(inout, (tm->tm_year < 0 ? bc_STR : ad_STR));
                return 1;

            }
            else if (flag == FROM_CHAR)
            {
                if (strncmp(inout, bc_STR, 2) == 0)
                    tmfc->bc = TRUE;
                return 1;
            }
            break;
        case DCH_MONTH:
            strcpy(inout, months_full[tm->tm_mon - 1]);
            sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, str_toupper(inout));
            if (S_FM(suf))
                return strlen(p_inout) - 1;
            else
                return 8;

        case DCH_Month:
            sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, months_full[tm->tm_mon - 1]);
            if (S_FM(suf))
                return strlen(p_inout) - 1;
            else
                return 8;

        case DCH_month:
            sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, months_full[tm->tm_mon - 1]);
            *inout = tolower(*inout);
            if (S_FM(suf))
                return strlen(p_inout) - 1;
            else
                return 8;

        case DCH_MON:
            strcpy(inout, months[tm->tm_mon - 1]);
            inout = str_toupper(inout);
            return 2;

        case DCH_Mon:
            strcpy(inout, months[tm->tm_mon - 1]);
            return 2;

        case DCH_mon:
            strcpy(inout, months[tm->tm_mon - 1]);
            *inout = tolower(*inout);
            return 2;

        case DCH_MM:
            if (flag == 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));
                if (S_FM(suf) || S_THth(suf))
                    return strlen(p_inout) - 1;
                else
                    return 1;

            }
            else if (flag == FROM_CHAR)
            {
                if (S_FM(suf))
                {
                    sscanf(inout, "%d", &tmfc->mm);
                    return int4len((int4) tmfc->mm) - 1 + SKIP_THth(suf);
                }
                else
                {
                    sscanf(inout, "%02d", &tmfc->mm);
                    return 1 + SKIP_THth(suf);
                }
            }
            break;
        case DCH_DAY:
            strcpy(inout, days[tm->tm_wday]);
            sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, str_toupper(inout));
            if (S_FM(suf))
                return strlen(p_inout) - 1;
            else
                return 8;

        case DCH_Day:
            sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, days[tm->tm_wday]);
            if (S_FM(suf))
                return strlen(p_inout) - 1;
            else
                return 8;

        case DCH_day:
            sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, days[tm->tm_wday]);
            *inout = tolower(*inout);
            if (S_FM(suf))
                return strlen(p_inout) - 1;
            else
                return 8;

        case DCH_DY:
            strcpy(inout, days[tm->tm_wday]);
            inout = str_toupper(inout);
            return 2;

        case DCH_Dy:
            strcpy(inout, days[tm->tm_wday]);
            return 2;

        case DCH_dy:
            strcpy(inout, days[tm->tm_wday]);
            *inout = tolower(*inout);
            return 2;

        case DCH_DDD:
            if (flag == TO_CHAR)
            {
                sprintf(inout, "%0*d", S_FM(suf) ? 0 : 3, tm->tm_yday);
                if (S_THth(suf))
                    str_numth(p_inout, inout, S_TH_TYPE(suf));
                if (S_FM(suf) || S_THth(suf))
                    return strlen(p_inout) - 1;
                else
                    return 2;

            }
            else if (flag == FROM_CHAR)
            {
                if (S_FM(suf))
                {
                    sscanf(inout, "%d", &tmfc->ddd);
                    return int4len((int4) tmfc->ddd) - 1 + SKIP_THth(suf);
                }
                else
                {
                    sscanf(inout, "%03d", &tmfc->ddd);
                    return 2 + SKIP_THth(suf);
                }
            }
            break;
        case DCH_DD:
            if (flag == 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));
                if (S_FM(suf) || S_THth(suf))
                    return strlen(p_inout) - 1;
                else
                    return 1;

            }
            else if (flag == FROM_CHAR)
            {
                if (S_FM(suf))
                {
                    sscanf(inout, "%d", &tmfc->dd);
                    return int4len((int4) tmfc->dd) - 1 + SKIP_THth(suf);
                }
                else
                {
                    sscanf(inout, "%02d", &tmfc->dd);
                    return 1 + SKIP_THth(suf);
                }
            }
            break;
        case DCH_D:
            if (flag == TO_CHAR)
            {
                sprintf(inout, "%d", tm->tm_wday + 1);
                if (S_THth(suf))
                {
                    str_numth(p_inout, inout, S_TH_TYPE(suf));
                    return 2;
                }
                return 0;
            }
            else if (flag == FROM_CHAR)
            {
                sscanf(inout, "%1d", &tmfc->d);
                return 0 + SKIP_THth(suf);
            }
            break;
        case DCH_WW:
            if (flag == 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));
                if (S_FM(suf) || S_THth(suf))
                    return strlen(p_inout) - 1;
                else
                    return 1;

            }
            else if (flag == FROM_CHAR)
            {
                if (S_FM(suf))
                {
                    sscanf(inout, "%d", &tmfc->ww);
                    return int4len((int4) tmfc->ww) - 1 + SKIP_THth(suf);
                }
                else
                {
                    sscanf(inout, "%02d", &tmfc->ww);
                    return 1 + SKIP_THth(suf);
                }
            }
            break;
        case DCH_IW:
            if (flag == 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));
                if (S_FM(suf) || S_THth(suf))
                    return strlen(p_inout) - 1;
                else
                    return 1;

            }
            else if (flag == FROM_CHAR)
            {
                if (S_FM(suf))
                {
                    sscanf(inout, "%d", &tmfc->iw);
                    return int4len((int4) tmfc->iw) - 1 + SKIP_THth(suf);
                }
                else
                {
                    sscanf(inout, "%02d", &tmfc->iw);
                    return 1 + SKIP_THth(suf);
                }
            }
            break;
        case DCH_Q:
            if (flag == TO_CHAR)
            {
                sprintf(inout, "%d", (tm->tm_mon - 1) / 3 + 1);
                if (S_THth(suf))
                {
                    str_numth(p_inout, inout, S_TH_TYPE(suf));
                    return 2;
                }
                return 0;

            }
            else if (flag == FROM_CHAR)
            {
                sscanf(inout, "%1d", &tmfc->q);
                return 0 + SKIP_THth(suf);
            }
            break;
        case DCH_CC:
            if (flag == TO_CHAR)
            {
                i = tm->tm_year / 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) - 1;

            }
            else if (flag == FROM_CHAR)
            {
                sscanf(inout, "%d", &tmfc->cc);
                return int4len((int4) tmfc->cc) + SKIP_THth(suf) -1;
            }
            break;
        case DCH_Y_YYY:
            if (flag == TO_CHAR)
            {
                i = YEAR_ABS(tm->tm_year) / 1000;
                sprintf(inout, "%d,%03d", i, YEAR_ABS(tm->tm_year) - (i * 1000));
                if (S_THth(suf))
                    str_numth(p_inout, inout, S_TH_TYPE(suf));
                return strlen(p_inout) - 1;
            }
            else if (flag == FROM_CHAR)
            {
                int            cc;

                sscanf(inout, "%d,%03d", &cc, &tmfc->yyyy);
                tmfc->yyyy += (cc * 1000);

                if (!S_FM(suf) && tmfc->yyyy <= 9999 && tmfc->yyyy >= -9999)
                    len = 5;
                else
                    len = int4len((int4) tmfc->yyyy) + 1;
                len += SKIP_THth(suf);
                return len - 1;
            }
            break;
        case DCH_YYYY:
            if (flag == TO_CHAR)
            {
                if (tm->tm_year <= 9999 && tm->tm_year >= -9998)
                    sprintf(inout, "%0*d", S_FM(suf) ? 0 : 4, YEAR_ABS(tm->tm_year));
                else
                    sprintf(inout, "%d", YEAR_ABS(tm->tm_year));
                if (S_THth(suf))
                    str_numth(p_inout, inout, S_TH_TYPE(suf));
                return strlen(p_inout) - 1;
            }
            else if (flag == FROM_CHAR)
            {
                sscanf(inout, "%d", &tmfc->yyyy);
                if (!S_FM(suf) && tmfc->yyyy <= 9999 && tmfc->yyyy >= -9999)
                    len = 4;
                else
                    len = int4len((int4) tmfc->yyyy);
                len += SKIP_THth(suf);
                return len - 1;
            }
            break;
        case DCH_YYY:
            if (flag == TO_CHAR)
            {
                sprintf(buff, "%03d", YEAR_ABS(tm->tm_year));
                i = strlen(buff);
                strcpy(inout, buff + (i - 3));
                if (S_THth(suf))
                {
                    str_numth(p_inout, inout, S_TH_TYPE(suf));
                    return 4;
                }
                return 2;

            }
            else if (flag == FROM_CHAR)
            {
                sscanf(inout, "%03d", &tmfc->yyyy);
                return 2 + SKIP_THth(suf);
            }
            break;
        case DCH_YY:
            if (flag == TO_CHAR)
            {
                sprintf(buff, "%02d", YEAR_ABS(tm->tm_year));
                i = strlen(buff);
                strcpy(inout, buff + (i - 2));
                if (S_THth(suf))
                {
                    str_numth(p_inout, inout, S_TH_TYPE(suf));
                    return 3;
                }
                return 1;

            }
            else if (flag == FROM_CHAR)
            {
                sscanf(inout, "%02d", &tmfc->yyyy);
                return 1 + SKIP_THth(suf);
            }
            break;
        case DCH_Y:
            if (flag == TO_CHAR)
            {
                sprintf(buff, "%1d", YEAR_ABS(tm->tm_year));
                i = strlen(buff);
                strcpy(inout, buff + (i - 1));
                if (S_THth(suf))
                {
                    str_numth(p_inout, inout, S_TH_TYPE(suf));
                    return 2;
                }
                return 0;

            }
            else if (flag == FROM_CHAR)
            {
                sscanf(inout, "%1d", &tmfc->yyyy);
                return 0 + SKIP_THth(suf);
            }
            break;
        case DCH_RM:
            if (flag == TO_CHAR)
            {
                sprintf(inout, "%*s", S_FM(suf) ? 0 : -4,
                        rm_months_upper[12 - tm->tm_mon]);
                if (S_FM(suf))
                    return strlen(p_inout) - 1;
                else
                    return 3;

            }
            else if (flag == FROM_CHAR)
            {
                tmfc->mm = 12 - seq_search(inout, rm_months_upper, ALL_UPPER, FULL_SIZ, &len);
                CHECK_SEQ_SEARCH(len, "RM");
                if (S_FM(suf))
                    return len - 1;
                else
                    return 3;
            }
            break;
        case DCH_rm:
            if (flag == TO_CHAR)
            {
                sprintf(inout, "%*s", S_FM(suf) ? 0 : -4,
                        rm_months_lower[12 - tm->tm_mon]);
                if (S_FM(suf))
                    return strlen(p_inout) - 1;
                else
                    return 3;

            }
            else if (flag == FROM_CHAR)
            {
                tmfc->mm = 12 - seq_search(inout, rm_months_lower, ALL_UPPER, FULL_SIZ, &len);
                CHECK_SEQ_SEARCH(len, "rm");
                if (S_FM(suf))
                    return len - 1;
                else
                    return 3;
            }
            break;
        case DCH_W:
            if (flag == 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 2;
                }
                return 0;
            }
            else if (flag == FROM_CHAR)
            {
                sscanf(inout, "%1d", &tmfc->w);
                return 0 + SKIP_THth(suf);
            }
            break;
        case DCH_J:
            if (flag == 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) - 1;
            }
            else if (flag == FROM_CHAR)
            {
                sscanf(inout, "%d", &tmfc->j);
                return int4len((int4) tmfc->j) + SKIP_THth(suf) -1;
            }
            break;
    }
    return -1;
}

static DCHCacheEntry *
DCH_cache_getnew(char *str)
{
    DCHCacheEntry *ent = NULL;

    /* counter overload check  - paranoa? */
    if (DCHCounter + DCH_CACHE_FIELDS >= MAX_INT32)
    {
        DCHCounter = 0;

        for (ent = DCHCache; ent <= (DCHCache + DCH_CACHE_FIELDS); ent++)
            ent->age = (++DCHCounter);
    }

    /* ----------
     * Cache is full - needs remove any older entry
     * ----------
     */
    if (n_DCHCache > DCH_CACHE_FIELDS)
    {

        DCHCacheEntry *old = DCHCache + 0;

#ifdef DEBUG_TO_FROM_CHAR
        elog(DEBUG_elog_output, "Cache is full (%d)", n_DCHCache);
#endif
        for (ent = DCHCache; ent <= (DCHCache + DCH_CACHE_FIELDS); ent++)
        {
            if (ent->age < old->age)
                old = ent;
        }
#ifdef DEBUG_TO_FROM_CHAR
        elog(DEBUG_elog_output, "OLD: '%s' AGE: %d", old->str, old->age);
#endif
        strcpy(old->str, str);    /* check str size before this func. */
        /* old->format fill parser */
        old->age = (++DCHCounter);
        return old;

    }
    else
    {
#ifdef DEBUG_TO_FROM_CHAR
        elog(DEBUG_elog_output, "NEW (%d)", n_DCHCache);
#endif
        ent = DCHCache + n_DCHCache;
        strcpy(ent->str, str);    /* check str size before this func. */
        /* ent->format fill parser */
        ent->age = (++DCHCounter);
        ++n_DCHCache;
        return ent;
    }

    return (DCHCacheEntry *) NULL;        /* never */
}

static DCHCacheEntry *
DCH_cache_search(char *str)
{
    int            i = 0;
    DCHCacheEntry *ent;

    /* counter overload check  - paranoa? */
    if (DCHCounter + DCH_CACHE_FIELDS >= MAX_INT32)
    {
        DCHCounter = 0;

        for (ent = DCHCache; ent <= (DCHCache + DCH_CACHE_FIELDS); ent++)
            ent->age = (++DCHCounter);
    }

    for (ent = DCHCache; ent <= (DCHCache + DCH_CACHE_FIELDS); ent++)
    {
        if (i == n_DCHCache)
            break;
        if (strcmp(ent->str, str) == 0)
        {
            ent->age = (++DCHCounter);
            return ent;
        }
        i++;
    }

    return (DCHCacheEntry *) NULL;
}


/****************************************************************************
 *                Public routines
 ***************************************************************************/

/* -------------------
 * TIMESTAMP to_char()
 * -------------------
 */
Datum
timestamp_to_char(PG_FUNCTION_ARGS)
{
    Timestamp    dt = PG_GETARG_TIMESTAMP(0);
    text        *fmt = PG_GETARG_TEXT_P(1);
    text        *result,
            *result_tmp;

    FormatNode    *format;
    char        *str;
    double        fsec;
    int        len = 0,
            tz,
            flag = 0,
            x = 0;

    len = VARSIZE(fmt) - VARHDRSZ;

    if (len <= 0 || TIMESTAMP_NOT_FINITE(dt))
        PG_RETURN_NULL();

    ZERO_tm(tm);
    tzn = NULL;

    if (TIMESTAMP_IS_EPOCH(dt))
    {
        x = timestamp2tm(SetTimestamp(dt), NULL, tm, &fsec, NULL);

    }
    else if (TIMESTAMP_IS_CURRENT(dt))
    {
        x = timestamp2tm(SetTimestamp(dt), &tz, tm, &fsec, &tzn);

    }
    else
        x = timestamp2tm(dt, &tz, tm, &fsec, &tzn);

    if (x != 0)
        elog(ERROR, "to_char(): Unable to convert timestamp to tm");

    tm->tm_wday = (date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) + 1) % 7;
    tm->tm_yday = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - date2j(tm->tm_year, 1, 1) + 1;

    /* ----------
     * Convert VARDATA() to string
     * ----------
     */
    str = (char *) palloc(len + 1);
    memcpy(str, VARDATA(fmt), len);
    *(str + len) = '\0';

    /* ----------
     * Allocate result
     * ----------
     */
    result = (text *) palloc((len * DCH_MAX_ITEM_SIZ) + 1 + VARHDRSZ);

    /* ----------
     * Allocate new memory if format picture is bigger than static cache
     * and not use cache (call parser always) - flag=1 show this variant
     * ----------
     */
    if (len > DCH_CACHE_SIZE)
    {
        format = (FormatNode *) palloc((len + 1) * sizeof(FormatNode));
        flag = 1;

        parse_format(format, str, DCH_keywords,
                     DCH_suff, DCH_index, DCH_TYPE, NULL);

        (format + len)->type = NODE_TYPE_END;    /* Paranoa? */

    }
    else
    {

        /* ----------
         * Use cache buffers
         * ----------
         */
        DCHCacheEntry *ent;

        flag = 0;

        if ((ent = DCH_cache_search(str)) == NULL)
        {

            ent = DCH_cache_getnew(str);

            /* ----------
             * Not in the cache, must run parser and save a new
             * format-picture to the cache.
             * ----------
             */
            parse_format(ent->format, str, DCH_keywords,
                     DCH_suff, DCH_index, DCH_TYPE, NULL);

            (ent->format + len)->type = NODE_TYPE_END;    /* Paranoa? */

#ifdef DEBUG_TO_FROM_CHAR
            /* dump_node(ent->format, len); */
            /* dump_index(DCH_keywords, DCH_index);  */
#endif
        }
        format = ent->format;
    }

    DCH_processor(format, VARDATA(result), TO_CHAR);

    if (flag)
        pfree(format);

    pfree(str);

    /* ----------
     * for result is allocated max memory, which current format-picture
     * needs, now it must be re-allocate to result real size
     * ----------
     */
    if (!(len = strlen(VARDATA(result))))
    {
        pfree(result);
        PG_RETURN_NULL();
    }

    result_tmp = result;
    result = (text *) palloc(len + 1 + VARHDRSZ);

    strcpy(VARDATA(result), VARDATA(result_tmp));
    VARATT_SIZEP(result) = len + VARHDRSZ;
    pfree(result_tmp);

    PG_RETURN_TEXT_P(result);
}


/* ---------------------
 * TO_TIMESTAMP()
 *
 * Make Timestamp from date_str which is formated at argument 'fmt'
 * ( to_timestamp is reverse to_char() )
 * ---------------------
 */
Datum
to_timestamp(PG_FUNCTION_ARGS)
{
    text        *date_str = PG_GETARG_TEXT_P(0);
    text        *fmt = PG_GETARG_TEXT_P(1);
    FormatNode    *format;
    int        flag = 0;
    Timestamp    result;
    char        *str;
    int        len = 0,
            fsec = 0,
            tz = 0;

    ZERO_tm(tm);
    ZERO_tmfc(tmfc);

    len = VARSIZE(fmt) - VARHDRSZ;

    if (len)
    {

        /* ----------
         * Convert VARDATA() to string
         * ----------
         */
        str = (char *) palloc(len + 1);
        memcpy(str, VARDATA(fmt), len);
        *(str + len) = '\0';

        /* ----------
         * Allocate new memory if format picture is bigger than static cache
         * and not use cache (call parser always) - flag=1 show this variant
         * ----------
         */
        if (len > DCH_CACHE_SIZE)
        {
            format = (FormatNode *) palloc((len + 1) * sizeof(FormatNode));
            flag = 1;

            parse_format(format, str, DCH_keywords,
                         DCH_suff, DCH_index, DCH_TYPE, NULL);

            (format + len)->type = NODE_TYPE_END;        /* Paranoa? */
        }
        else
        {

            /* ----------
             * Use cache buffers
             * ----------
             */
            DCHCacheEntry *ent;

            flag = 0;

            if ((ent = DCH_cache_search(str)) == NULL)
            {

                ent = DCH_cache_getnew(str);

                /* ----------
                 * Not in the cache, must run parser and save a new
                 * format-picture to the cache.
                 * ----------
                     */
                parse_format(ent->format, str, DCH_keywords,
                             DCH_suff, DCH_index, DCH_TYPE, NULL);

                (ent->format + len)->type = NODE_TYPE_END;        /* Paranoa? */
#ifdef DEBUG_TO_FROM_CHAR
                /* dump_node(ent->format, len); */
                /* dump_index(DCH_keywords, DCH_index); */
#endif
            }
            format = ent->format;
        }

        /* ----------
         * Call action for each node in FormatNode tree
         * ----------
         */
#ifdef DEBUG_TO_FROM_CHAR
        /* dump_node(format, len); */
#endif
        VARDATA(date_str)[VARSIZE(date_str) - VARHDRSZ] = '\0';
        DCH_processor(format, VARDATA(date_str), FROM_CHAR);

        pfree(str);

        if (flag)
            pfree(format);
    }

    /* --------------------------------------------------------------
     * Convert values that user define for FROM_CHAR (to_date/to_timestamp)
     * to standard 'tm'
     * ----------
     */
#ifdef DEBUG_TO_FROM_CHAR
    NOTICE_TMFC;
#endif
    if (tmfc->ssss)
    {
        int x;

        if (tmfc->ssss > 3600)
            tm->tm_sec = x - ((tm->tm_min = (x = tmfc->ssss -
                ((tm->tm_hour= tmfc->ssss / 3600) * 3600)) / 60) * 60);
        else if (tmfc->ssss > 60)
            tm->tm_sec =  tmfc->ssss - ((tm->tm_min =  tmfc->ssss / 60) * 60);
        else
            tm->tm_sec = tmfc->ssss;
    }

    if (tmfc->cc)
        tm->tm_year = (tmfc->cc-1) * 100;

    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)    tm->tm_min = tmfc->mi;
    if (tmfc->hh)    tm->tm_hour = tmfc->hh;

    if (tmfc->pm || tmfc->am)
    {
        if (tm->tm_hour < 1 || tm->tm_hour > 12)
            elog(ERROR, "to_timestamp(): AM/PM hour must be between 1 and 12");

        if (tmfc->pm && tm->tm_hour < 12)
            tm->tm_hour += 12;

        else if (tmfc->am && tm->tm_hour == 12)
            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;
    }

    if (tmfc->yyyy)
        tm->tm_year = tmfc->yyyy;

    if (tmfc->j)
        j2date(tmfc->j, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);

    if (tmfc->bc && tm->tm_year > 0)
        tm->tm_year = -(tm->tm_year);

    if (tm->tm_year < 0)
        tm->tm_year = tm->tm_year + 1;

    if (tmfc->iw)
        isoweek2date(tmfc->iw, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);

    if (tmfc->d)    tm->tm_wday = tmfc->d;
    if (tmfc->dd)    tm->tm_mday = tmfc->dd;
    if (tmfc->ddd)    tm->tm_yday = tmfc->ddd;
    if (tmfc->mm)    tm->tm_mon  = tmfc->mm;

    /*
     * we not ignore DDD
     */
    if (tmfc->ddd && (tm->tm_mon <=1 || tm->tm_mday <=1))
    {
        /* count mday and mon from yday */
        int *y, i;

        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 }};

         if (!tm->tm_year)
             elog(ERROR, "to_timestamp() cat't convert yday without year information");

         y = ysum[ isleap(tm->tm_year) ];

         for (i=0; i <= 11; i++)
         {
            if (tm->tm_yday < y[i])
                break;
         }
         if (tm->tm_mon <=1)
             tm->tm_mon = i+1;

         if (tm->tm_mday <=1)
             tm->tm_mday = i == 0 ?    tm->tm_yday :
                         tm->tm_yday - y[i-1];
    }

    /* -------------------------------------------------------------- */

#ifdef DEBUG_TO_FROM_CHAR
    NOTICE_TM;
#endif
    if (IS_VALID_UTIME(tm->tm_year, tm->tm_mon, tm->tm_mday))
    {

#ifdef USE_POSIX_TIME
        tm->tm_isdst = -1;
        tm->tm_year -= 1900;
        tm->tm_mon -= 1;

#ifdef DEBUG_TO_FROM_CHAR
        elog(DEBUG_elog_output, "TO-FROM_CHAR: Call mktime()");
        NOTICE_TM;
#endif
        mktime(tm);
        tm->tm_year += 1900;
        tm->tm_mon += 1;

#if defined(HAVE_TM_ZONE)
        tz = -(tm->tm_gmtoff);    /* tm_gmtoff is Sun/DEC-ism */
#elif defined(HAVE_INT_TIMEZONE)

#ifdef __CYGWIN__
        tz = (tm->tm_isdst ? (_timezone - 3600) : _timezone);
#else
        tz = (tm->tm_isdst ? (timezone - 3600) : timezone);
#endif

#else
#error USE_POSIX_TIME is defined but neither HAVE_TM_ZONE or HAVE_INT_TIMEZONE are defined
#endif

#else                            /* !USE_POSIX_TIME */
        tz = CTimeZone;
#endif
    }
    else
    {
        tm->tm_isdst = 0;
        tz = 0;
    }
#ifdef DEBUG_TO_FROM_CHAR
    NOTICE_TM;
#endif
    if (tm2timestamp(tm, fsec, &tz, &result) != 0)
        elog(ERROR, "to_timestamp(): can't convert 'tm' to timestamp.");

    PG_RETURN_TIMESTAMP(result);
}

/* ----------
 * TO_DATE
 *    Make Date from date_str which is formated at argument 'fmt'
 * ----------
 */
Datum
to_date(PG_FUNCTION_ARGS)
{
    /* Quick hack: since our inputs are just like to_timestamp,
     * hand over the whole input info struct...
     */
    return DirectFunctionCall1(timestamp_date, to_timestamp(fcinfo));
}

/**********************************************************************
 *    the NUMBER version part
 *********************************************************************/


static char *
fill_str(char *str, int c, int max)
{
    memset(str, c, max);
    *(str + max + 1) = '\0';
    return str;
}

#define zeroize_NUM(_n) \
do { \
    (_n)->flag        = 0;    \
    (_n)->lsign        = 0;    \
    (_n)->pre        = 0;    \
    (_n)->post        = 0;    \
    (_n)->pre_lsign_num    = 0;    \
    (_n)->need_locale    = 0;    \
    (_n)->multi        = 0;    \
    (_n)->zero_start    = 0;    \
    (_n)->zero_end        = 0;    \
} while(0)

static NUMCacheEntry *
NUM_cache_getnew(char *str)
{
    NUMCacheEntry *ent = NULL;

    /* counter overload check  - paranoa? */
    if (NUMCounter + NUM_CACHE_FIELDS >= MAX_INT32)
    {
        NUMCounter = 0;

        for (ent = NUMCache; ent <= (NUMCache + NUM_CACHE_FIELDS); ent++)
            ent->age = (++NUMCounter);
    }

    /* ----------
     * Cache is full - needs remove any older entry
     * ----------
     */
    if (n_NUMCache > NUM_CACHE_FIELDS)
    {

        NUMCacheEntry *old = NUMCache + 0;

#ifdef DEBUG_TO_FROM_CHAR
        elog(DEBUG_elog_output, "Cache is full (%d)", n_NUMCache);
#endif

        for (ent = NUMCache; ent <= (NUMCache + NUM_CACHE_FIELDS); ent++)
        {
            if (ent->age < old->age)
                old = ent;
        }
#ifdef DEBUG_TO_FROM_CHAR
        elog(DEBUG_elog_output, "OLD: '%s' AGE: %d", old->str, old->age);
#endif
        strcpy(old->str, str);    /* check str size before this func. */
        /* old->format fill parser */
        old->age = (++NUMCounter);

        ent = old;

    }
    else
    {
#ifdef DEBUG_TO_FROM_CHAR
        elog(DEBUG_elog_output, "NEW (%d)", n_NUMCache);
#endif
        ent = NUMCache + n_NUMCache;
        strcpy(ent->str, str);    /* check str size before this func. */
        /* ent->format fill parser */
        ent->age = (++NUMCounter);
        ++n_NUMCache;
    }

    zeroize_NUM(&ent->Num);

    return ent;                    /* never */
}

static NUMCacheEntry *
NUM_cache_search(char *str)
{
    int i = 0;
    NUMCacheEntry *ent;

    /* counter overload check  - paranoa? */
    if (NUMCounter + NUM_CACHE_FIELDS >= MAX_INT32)
    {
        NUMCounter = 0;

        for (ent = NUMCache; ent <= (NUMCache + NUM_CACHE_FIELDS); ent++)
            ent->age = (++NUMCounter);
    }

    for (ent = NUMCache; ent <= (NUMCache + NUM_CACHE_FIELDS); ent++)
    {
        if (i == n_NUMCache)
            break;
        if (strcmp(ent->str, str) == 0)
        {
            ent->age = (++NUMCounter);
            return ent;
        }
        i++;
    }

    return (NUMCacheEntry *) NULL;
}

/* ----------
 * Cache routine for NUM to_char version
 * ----------
 */
static FormatNode *
NUM_cache(int len, NUMDesc *Num, char *pars_str, int *flag)
{
    FormatNode *format = NULL;
    char       *str;

    /* ----------
     * Convert VARDATA() to string
     * ----------
     */
    str = (char *) palloc(len + 1);
    memcpy(str, pars_str, len);
    *(str + len) = '\0';

    /* ----------
     * Allocate new memory if format picture is bigger than static cache
     * and not use cache (call parser always) - flag=1 show this variant
     * ----------
         */
    if (len > NUM_CACHE_SIZE)
    {

        format = (FormatNode *) palloc((len + 1) * sizeof(FormatNode));
        *flag = 1;

        zeroize_NUM(Num);

        parse_format(format, str, NUM_keywords,
                     NULL, NUM_index, NUM_TYPE, Num);

        (format + len)->type = NODE_TYPE_END;    /* Paranoa? */

    }
    else
    {

        /* ----------
         * Use cache buffers
         * ----------
         */
        NUMCacheEntry *ent;

        flag = 0;

        if ((ent = NUM_cache_search(str)) == NULL)
        {

            ent = NUM_cache_getnew(str);

            /* ----------
             * Not in the cache, must run parser and save a new
             * format-picture to the cache.
             * ----------
                 */
            parse_format(ent->format, str, NUM_keywords,
                         NULL, NUM_index, NUM_TYPE, &ent->Num);

            (ent->format + len)->type = NODE_TYPE_END;    /* Paranoa? */

        }

        format = ent->format;

        /* ----------
         * Copy cache to used struct
         * ----------
         */
        Num->flag = ent->Num.flag;
        Num->lsign = ent->Num.lsign;
        Num->pre = ent->Num.pre;
        Num->post = ent->Num.post;
        Num->pre_lsign_num = ent->Num.pre_lsign_num;
        Num->need_locale = ent->Num.need_locale;
        Num->multi = ent->Num.multi;
        Num->zero_start = ent->Num.zero_start;
        Num->zero_end = ent->Num.zero_end;
    }

#ifdef DEBUG_TO_FROM_CHAR
    /* dump_node(format, len); */
    dump_index(NUM_keywords, NUM_index);
#endif

    pfree(str);
    return format;
}


static char *
int_to_roman(int number)
{
    int    len = 0,
        num = 0,
        set = 0;
    char    *p = NULL,
        *result,
        numstr[5];

    result = (char *) palloc(16);
    *result = '\0';

    if (number > 3999 || number < 1)
    {
        fill_str(result, '#', 15);
        return result;
    }
    len = sprintf(numstr, "%d", number);

    for (p = numstr; *p != '\0'; p++, --len)
    {
        num = *p - 49;            /* 48 ascii + 1 */
        if (num < 0)
            continue;
        if (num == -1 && set == 0)
            continue;
        set = 1;

        if (len > 3)
        {
            while (num-- != -1)
                strcat(result, "M");
        }
        else
        {
            if (len == 3)
                strcat(result, rm100[num]);
            else if (len == 2)
                strcat(result, rm10[num]);
            else if (len == 1)
                strcat(result, rm1[num]);
        }
    }
    return result;
}



/* ----------
 * Locale
 * ----------
 */
static void
NUM_prepare_locale(NUMProc *Np)
{

#ifdef USE_LOCALE

    if (Np->Num->need_locale)
    {

        struct lconv *lconv;

        /* ----------
         * Get locales
         * ----------
         */
        lconv = PGLC_localeconv();

        /* ----------
         * Positive / Negative number sign
         * ----------
         */
        if (lconv->negative_sign && *lconv->negative_sign)
            Np->L_negative_sign = lconv->negative_sign;
        else
            Np->L_negative_sign = "-";

        if (lconv->positive_sign && *lconv->positive_sign)
            Np->L_positive_sign = lconv->positive_sign;
        else
            Np->L_positive_sign = "+";

        /* ----------
         * Number thousands separator
         * ----------
         */
        if (lconv->thousands_sep && *lconv->thousands_sep)
            Np->L_thousands_sep = lconv->thousands_sep;
        else
            Np->L_thousands_sep = ",";

        /* ----------
         * Number decimal point
         * ----------
         */
        if (lconv->decimal_point && *lconv->decimal_point)
            Np->decimal = lconv->decimal_point;
        else
            Np->decimal = ".";

        /* ----------
         * Currency symbol
         * ----------
         */
        if (lconv->currency_symbol && *lconv->currency_symbol)
            Np->L_currency_symbol = lconv->currency_symbol;
        else
            Np->L_currency_symbol = " ";


        if (!IS_LDECIMAL(Np->Num))
            Np->decimal = ".";
    }
    else
    {

#endif
        /* ----------
         * Default values
         * ----------
         */
        Np->L_negative_sign = "-";
        Np->L_positive_sign = "+";
        Np->decimal = ".";
        Np->L_thousands_sep = ",";
        Np->L_currency_symbol = " ";

#ifdef USE_LOCALE
    }
#endif
}

/* ----------
 * Return pointer of last relevant number after decimal point
 *    12.0500 --> last relevant is '5'
 * ----------
 */
static char *
get_last_relevant_decnum(char *num)
{
    char       *result,
           *p = strchr(num, '.');

#ifdef DEBUG_TO_FROM_CHAR
    elog(DEBUG_elog_output, "CALL: get_last_relevant_decnum()");
#endif

    if (!p)
        p = num;
    result = p;

    while (*(++p))
    {
        if (*p != '0')
            result = p;
    }

    return result;
}

/* ----------
 * Number extraction for TO_NUMBER()
 * ----------
 */
static void
NUM_numpart_from_char(NUMProc *Np, int id, int plen)
{

#ifdef DEBUG_TO_FROM_CHAR
    elog(DEBUG_elog_output, " --- scan start --- ");
#endif

    if (*Np->inout_p == ' ')
        Np->inout_p++;

#define OVERLOAD_TEST    (Np->inout_p >= Np->inout + plen)

    if (*Np->inout_p == ' ')
        Np->inout_p++;

    if (OVERLOAD_TEST)
        return;

    /* ----------
     * read sign
     * ----------
     */
    if (*Np->number == ' ' && (id == NUM_0 || id == NUM_9 || NUM_S))
    {

#ifdef DEBUG_TO_FROM_CHAR
        elog(DEBUG_elog_output, "Try read sign (%c).", *Np->inout_p);
#endif
        /* ----------
         * locale sign
         * ----------
         */
        if (IS_LSIGN(Np->Num))
        {

            int            x = strlen(Np->L_negative_sign);

#ifdef DEBUG_TO_FROM_CHAR
            elog(DEBUG_elog_output, "Try read locale sign (%c).", *Np->inout_p);
#endif
            if (!strncmp(Np->inout_p, Np->L_negative_sign, x))
            {
                Np->inout_p += x - 1;
                *Np->number = '-';
                return;
            }

            x = strlen(Np->L_positive_sign);
            if (!strncmp(Np->inout_p, Np->L_positive_sign, x))
            {
                Np->inout_p += x - 1;
                *Np->number = '+';
                return;
            }
        }

#ifdef DEBUG_TO_FROM_CHAR
        elog(DEBUG_elog_output, "Try read sipmle sign (%c).", *Np->inout_p);
#endif
        /* ----------
         * simple + - < >
         * ----------
         */
        if (*Np->inout_p == '-' || (IS_BRACKET(Np->Num) &&
                                    *Np->inout_p == '<'))
        {

            *Np->number = '-';    /* set - */
            Np->inout_p++;

        }
        else if (*Np->inout_p == '+')
        {

            *Np->number = '+';    /* set + */
            Np->inout_p++;
        }
    }

    if (OVERLOAD_TEST)
        return;

    /* ----------
     * read digit
     * ----------
     */
    if (isdigit((unsigned char) *Np->inout_p))
    {

        if (Np->read_dec && Np->read_post == Np->Num->post)
            return;

        *Np->number_p = *Np->inout_p;
        Np->number_p++;

        if (Np->read_dec)
            Np->read_post++;

#ifdef DEBUG_TO_FROM_CHAR
        elog(DEBUG_elog_output, "Read digit (%c).", *Np->inout_p);
#endif

        /* ----------
         * read decimal point
         * ----------
         */
    }
    else if (IS_DECIMAL(Np->Num))
    {

#ifdef DEBUG_TO_FROM_CHAR
        elog(DEBUG_elog_output, "Try read decimal point (%c).", *Np->inout_p);
#endif
        if (*Np->inout_p == '.')
        {

            *Np->number_p = '.';
            Np->number_p++;
            Np->read_dec = TRUE;

        }
        else
        {
            int    x = strlen(Np->decimal);

#ifdef DEBUG_TO_FROM_CHAR
            elog(DEBUG_elog_output, "Try read locale point (%c).", *Np->inout_p);
#endif
            if (!strncmp(Np->inout_p, Np->decimal, x))
            {
                Np->inout_p += x - 1;
                *Np->number_p = '.';
                Np->number_p++;
                Np->read_dec = TRUE;
            }
        }
    }
}

/* ----------
 * Add digit or sign to number-string
 * ----------
 */
static void
NUM_numpart_to_char(NUMProc *Np, int id)
{
    if (IS_ROMAN(Np->Num))
        return;

    /* Note: in this elog() output not set '\0' in 'inout' */

#ifdef DEBUG_TO_FROM_CHAR

    /*
     * Np->num_curr is number of current item in format-picture, it is not
     * current position in inout!
     */
    elog(DEBUG_elog_output,

         "SIGN_WROTE: %d, CURRENT: %d, NUMBER_P: '%s', INOUT: '%s'",
         Np->sign_wrote,
         Np->num_curr,
         Np->number_p,
         Np->inout);
#endif
    Np->num_in = FALSE;

    /* ----------
     * Write sign
     * ----------
     */
    if (Np->num_curr == Np->sign_pos && Np->sign_wrote == FALSE)
    {

#ifdef DEBUG_TO_FROM_CHAR
        elog(DEBUG_elog_output, "Writing sign to position: %d", Np->num_curr);
#endif
        if (IS_LSIGN(Np->Num))
        {

            /* ----------
             * Write locale SIGN
             * ----------
             */
            if (Np->sign == '-')
                strcpy(Np->inout_p, Np->L_negative_sign);
            else
                strcpy(Np->inout_p, Np->L_positive_sign);
            Np->inout_p += strlen(Np->inout_p);

        }
        else if (IS_BRACKET(Np->Num))
        {
            *Np->inout_p = '<'; /* Write < */
            ++Np->inout_p;

        }
        else if (Np->sign == '+')
        {
            *Np->inout_p = ' '; /* Write + */
            ++Np->inout_p;

        }
        else if (Np->sign == '-')
        {                        /* Write - */
            *Np->inout_p = '-';
            ++Np->inout_p;
        }
        Np->sign_wrote = TRUE;

    }
    else if (Np->sign_wrote && IS_BRACKET(Np->Num) &&
             (Np->num_curr == Np->num_count + (Np->num_pre ? 1 : 0)
              + (IS_DECIMAL(Np->Num) ? 1 : 0)))
    {
        /* ----------
         * Write close BRACKET
         * ----------
         */
#ifdef DEBUG_TO_FROM_CHAR
        elog(DEBUG_elog_output, "Writing bracket to position %d", Np->num_curr);
#endif
        *Np->inout_p = '>';        /* Write '>' */
        ++Np->inout_p;
    }

    /* ----------
     * digits / FM / Zero / Dec. point
     * ----------
     */
    if (id == NUM_9 || id == NUM_0 || id == NUM_D || id == NUM_DEC ||
        (id == NUM_S && Np->num_curr < Np->num_pre))
    {

        if (Np->num_curr < Np->num_pre &&
            (Np->Num->zero_start > Np->num_curr || !IS_ZERO(Np->Num)))
        {

            /* ----------
             * Write blank space
             * ----------
             */
            if (!IS_FILLMODE(Np->Num))
            {
#ifdef DEBUG_TO_FROM_CHAR
                elog(DEBUG_elog_output, "Writing blank space to position %d", Np->num_curr);
#endif
                *Np->inout_p = ' ';        /* Write ' ' */
                ++Np->inout_p;
            }

        }
        else if (IS_ZERO(Np->Num) &&
                 Np->num_curr < Np->num_pre &&
                 Np->Num->zero_start <= Np->num_curr)
        {

            /* ----------
             * Write ZERO
             * ----------
             */
#ifdef DEBUG_TO_FROM_CHAR
            elog(DEBUG_elog_output, "Writing zero to position %d", Np->num_curr);
#endif
            *Np->inout_p = '0'; /* Write '0' */
            ++Np->inout_p;
            Np->num_in = TRUE;

        }
        else
        {

            /* ----------
            * Write Decinal point
            * ----------
            */
            if (*Np->number_p == '.')
            {

                if (!Np->last_relevant || *Np->last_relevant != '.')
                {
#ifdef DEBUG_TO_FROM_CHAR
                    elog(DEBUG_elog_output, "Writing decimal point to position %d", Np->num_curr);
#endif
                    strcpy(Np->inout_p, Np->decimal);    /* Write DEC/D */
                    Np->inout_p += strlen(Np->inout_p);

                    /*
                     * terrible Ora '0' -- 9.9 --> '0.'
                     */
                }
                else if (IS_FILLMODE(Np->Num) && *Np->number == '0' &&
                         Np->last_relevant && *Np->last_relevant == '.')
                {

                    strcpy(Np->inout_p, Np->decimal);    /* Write DEC/D */
                    Np->inout_p += strlen(Np->inout_p);
                }

            }
            else
            {

                /* ----------
                 * Write Digits
                 * ----------
                 */
                if (Np->last_relevant && Np->number_p > Np->last_relevant &&
                    id != NUM_0)
                    ;

                /*
                 * terrible Ora format: '0.1' -- 9.9 --> '  .1'
                 */
                else if (!IS_ZERO(Np->Num) && *Np->number == '0' &&
                         Np->number == Np->number_p && Np->Num->post != 0)
                {

                    if (!IS_FILLMODE(Np->Num))
                    {
                        *Np->inout_p = ' ';
                        ++Np->inout_p;

                        /*
                         * total terible Ora: '0' -- FM9.9 --> '0.'
                         */
                    }
                    else if (Np->last_relevant && *Np->last_relevant == '.')
                    {
                        *Np->inout_p = '0';
                        ++Np->inout_p;
                    }

                }
                else
                {
#ifdef DEBUG_TO_FROM_CHAR
                    elog(DEBUG_elog_output, "Writing digit '%c' to position %d", *Np->number_p, Np->num_curr);
#endif
                    *Np->inout_p = *Np->number_p;        /* Write DIGIT */
                    ++Np->inout_p;
                    Np->num_in = TRUE;
                }
            }
            ++Np->number_p;
        }
    }

    ++Np->num_curr;
}

static char *
NUM_processor(FormatNode *node, NUMDesc *Num, char *inout, char *number,
              int plen, int sign, int type)
{
    FormatNode *n;
    NUMProc    _Np,
        *Np = &_Np;

    Np->Num = Num;
    Np->type = type;
    Np->number = number;
    Np->inout = inout;
    Np->last_relevant = NULL;
    Np->read_post = 0;
    Np->read_dec = FALSE;

    if (Np->Num->zero_start)
        --Np->Num->zero_start;

    /* ----------
     * Roman correction
     * ----------
     */
    if (IS_ROMAN(Np->Num))
    {
        if (Np->type == FROM_CHAR)
            elog(ERROR, "to_number(): RN is not supported");

        Np->Num->lsign = Np->Num->pre_lsign_num = Np->Num->post =
            Np->Num->pre = Np->num_pre = Np->sign = 0;

        if (IS_FILLMODE(Np->Num))
        {
            Np->Num->flag = 0;
            Np->Num->flag |= NUM_F_FILLMODE;
        }
        else
            Np->Num->flag = 0;
        Np->Num->flag |= NUM_F_ROMAN;
    }

    /* ----------
     * Sign
     * ----------
     */
    if (type == FROM_CHAR)
    {
        Np->sign = FALSE;
        Np->sign_pos = -1;
    }
    else
    {
        Np->sign = sign;

        if (Np->sign != '-')
        {
            Np->Num->flag &= ~NUM_F_BRACKET;
            Np->Num->flag &= ~NUM_F_MINUS;
        }
        else if (Np->sign != '+')
            Np->Num->flag &= ~NUM_F_PLUS;

        if (Np->sign == '+' && IS_FILLMODE(Np->Num) && !IS_LSIGN(Np->Num))
            Np->sign_wrote = TRUE;        /* needn't sign */
        else
            Np->sign_wrote = FALSE;        /* need sign */

        Np->sign_pos = -1;

        if (Np->Num->lsign == NUM_LSIGN_PRE && Np->Num->pre == Np->Num->pre_lsign_num)
            Np->Num->lsign = NUM_LSIGN_POST;

        /* MI/PL/SG - write sign itself and not in number */
        if (IS_PLUS(Np->Num) || IS_MINUS(Np->Num))
            Np->sign_wrote = TRUE;        /* needn't sign */
    }

    /* ----------
     * Count
     * ----------
     */
    Np->num_count = Np->Num->post + Np->Num->pre - 1;

    if (type == TO_CHAR)
    {
        Np->num_pre = plen;

        if (IS_FILLMODE(Np->Num))
        {
            if (IS_DECIMAL(Np->Num))
                Np->last_relevant = get_last_relevant_decnum(
                            Np->number +
                            ((Np->Num->zero_end - Np->num_pre > 0) ?
                             Np->Num->zero_end - Np->num_pre : 0));
        }

        if (!Np->sign_wrote && Np->num_pre == 0)
            ++Np->num_count;

        if (!Np->sign_wrote)
        {

            /* ----------
             * Set SING position
             * ----------
             */
            if (Np->Num->lsign == NUM_LSIGN_POST)
            {
                Np->sign_pos = Np->num_count + (Np->num_pre ? 1 : 0);

                if (IS_DECIMAL(Np->Num))        /* decimal point correctio */
                    ++Np->sign_pos;
            }
            else if (IS_ZERO(Np->Num) && Np->num_pre > Np->Num->zero_start)
                Np->sign_pos = Np->Num->zero_start ? Np->Num->zero_start : 0;

            else
                Np->sign_pos = Np->num_pre && !IS_FILLMODE(Np->Num) ? Np->num_pre : 0;

            /* ----------
             * terrible Ora format
             * ----------
             */
            if (!IS_ZERO(Np->Num) && *Np->number == '0' &&
                !IS_FILLMODE(Np->Num) && Np->Num->post != 0)
            {

                ++Np->sign_pos;

                if (IS_LSIGN(Np->Num))
                {
                    if (Np->Num->lsign == NUM_LSIGN_PRE)
                        ++Np->sign_pos;
                    else
                        --Np->sign_pos;
                }
            }
        }

    }
    else
    {
        Np->num_pre = 0;
        *Np->number = ' ';        /* sign space */
        *(Np->number + 1) = '\0';
    }

    Np->num_in = 0;
    Np->num_curr = 0;

#ifdef DEBUG_TO_FROM_CHAR
    elog(DEBUG_elog_output,

         "\n\tNUM: '%s'\n\tPRE: %d\n\tPOST: %d\n\tNUM_COUNT: %d\n\tNUM_PRE: %d\n\tSIGN_POS: %d\n\tSIGN_WROTE:
%s\n\tZERO:%s\n\tZERO_START: %d\n\tZERO_END: %d\n\tLAST_RELEVANT: %s", 
         Np->number,
         Np->Num->pre,
         Np->Num->post,
         Np->num_count,
         Np->num_pre,
         Np->sign_pos,
         Np->sign_wrote ? "Yes" : "No",
         IS_ZERO(Np->Num) ? "Yes" : "No",
         Np->Num->zero_start,
         Np->Num->zero_end,
         Np->last_relevant ? Np->last_relevant : "<not set>"
        );
#endif

    /* ----------
     * Locale
     * ----------
     */
    NUM_prepare_locale(Np);

    /* ----------
     * Processor direct cycle
     * ----------
     */
    if (Np->type == FROM_CHAR)
        Np->number_p = Np->number + 1;    /* first char is space for sign */
    else if (Np->type == TO_CHAR)
        Np->number_p = Np->number;

    for (n = node, Np->inout_p = Np->inout; n->type != NODE_TYPE_END; n++)
    {

        if (Np->type == FROM_CHAR)
        {
            /* ----------
             * Check non-string inout end
             * ----------
             */
            if (Np->inout_p >= Np->inout + plen)
                break;
        }

        /* ----------
         * Format pictures actions
         * ----------
         */
        if (n->type == NODE_TYPE_ACTION)
        {

            /* ----------
             * Create/reading digit/zero/blank/sing
             * ----------
             */
            switch (n->key->id)
            {

                case NUM_9:
                case NUM_0:
                case NUM_DEC:
                case NUM_D:
                case NUM_S:
                case NUM_PR:
                    if (Np->type == TO_CHAR)
                    {
                        NUM_numpart_to_char(Np, n->key->id);
                        continue;        /* for() */
                    }
                    else
                    {
                        NUM_numpart_from_char(Np, n->key->id, plen);
                        break;    /* switch() case: */
                    }

                case NUM_COMMA:
                    if (Np->type == TO_CHAR)
                    {
                        if (!Np->num_in)
                        {
                            if (IS_FILLMODE(Np->Num))
                                continue;
                            else
                                *Np->inout_p = ' ';
                        }
                        else
                            *Np->inout_p = ',';

                    }
                    else if (Np->type == FROM_CHAR)
                    {
                        if (!Np->num_in)
                        {
                            if (IS_FILLMODE(Np->Num))
                                continue;
                        }
                    }
                    break;

                case NUM_G:
                    if (Np->type == TO_CHAR)
                    {
                        if (!Np->num_in)
                        {
                            if (IS_FILLMODE(Np->Num))
                                continue;
                            else
                            {
                                int            x = strlen(Np->L_thousands_sep);

                                memset(Np->inout_p, ' ', x);
                                Np->inout_p += x - 1;
                            }
                        }
                        else
                        {
                            strcpy(Np->inout_p, Np->L_thousands_sep);
                            Np->inout_p += strlen(Np->inout_p) - 1;
                        }

                    }
                    else if (Np->type == FROM_CHAR)
                    {
                        if (!Np->num_in)
                        {
                            if (IS_FILLMODE(Np->Num))
                                continue;
                        }
                        Np->inout_p += strlen(Np->L_thousands_sep) - 1;
                    }
                    break;

                case NUM_L:
                    if (Np->type == TO_CHAR)
                    {
                        strcpy(Np->inout_p, Np->L_currency_symbol);
                        Np->inout_p += strlen(Np->inout_p) - 1;

                    }
                    else if (Np->type == FROM_CHAR)
                        Np->inout_p += strlen(Np->L_currency_symbol) - 1;
                    break;

                case NUM_RN:
                    if (IS_FILLMODE(Np->Num))
                    {
                        strcpy(Np->inout_p, Np->number_p);
                        Np->inout_p += strlen(Np->inout_p) - 1;
                    }
                    else
                        Np->inout_p += sprintf(Np->inout_p, "%15s", Np->number_p) - 1;
                    break;

                case NUM_rn:
                    if (IS_FILLMODE(Np->Num))
                    {
                        strcpy(Np->inout_p, str_tolower(Np->number_p));
                        Np->inout_p += strlen(Np->inout_p) - 1;
                    }
                    else
                        Np->inout_p += sprintf(Np->inout_p, "%15s", str_tolower(Np->number_p)) - 1;
                    break;

                case NUM_th:
                    if (IS_ROMAN(Np->Num) || *Np->number == '#' ||
                        Np->sign == '-' || IS_DECIMAL(Np->Num))
                        continue;

                    if (Np->type == TO_CHAR)
                        strcpy(Np->inout_p, get_th(Np->number, TH_LOWER));
                    Np->inout_p += 1;
                    break;

                case NUM_TH:
                    if (IS_ROMAN(Np->Num) || *Np->number == '#' ||
                        Np->sign == '-' || IS_DECIMAL(Np->Num))
                        continue;

                    if (Np->type == TO_CHAR)
                        strcpy(Np->inout_p, get_th(Np->number, TH_UPPER));
                    Np->inout_p += 1;
                    break;

                case NUM_MI:
                    if (Np->type == TO_CHAR)
                    {
                        if (Np->sign == '-')
                            *Np->inout_p = '-';
                        else
                            *Np->inout_p = ' ';

                    }
                    else if (Np->type == FROM_CHAR)
                    {
                        if (*Np->inout_p == '-')
                            *Np->number = '-';
                    }
                    break;

                case NUM_PL:
                    if (Np->type == TO_CHAR)
                    {
                        if (Np->sign == '+')
                            *Np->inout_p = '+';
                        else
                            *Np->inout_p = ' ';

                    }
                    else if (Np->type == FROM_CHAR)
                    {
                        if (*Np->inout_p == '+')
                            *Np->number = '+';
                    }
                    break;

                case NUM_SG:
                    if (Np->type == TO_CHAR)
                        *Np->inout_p = Np->sign;

                    else if (Np->type == FROM_CHAR)
                    {
                        if (*Np->inout_p == '-')
                            *Np->number = '-';
                        else if (*Np->inout_p == '+')
                            *Np->number = '+';
                    }
                    break;


                default:
                    continue;
                    break;
            }

        }
        else
        {
            /* ----------
             * Remove to output char from input in TO_CHAR
             * ----------
             */
            if (Np->type == TO_CHAR)
                *Np->inout_p = n->character;
        }
        Np->inout_p++;
    }

    if (Np->type == TO_CHAR)
    {
        *Np->inout_p = '\0';
        return Np->inout;

    }
    else if (Np->type == FROM_CHAR)
    {

        if (*(Np->number_p - 1) == '.')
            *(Np->number_p - 1) = '\0';
        else
            *Np->number_p = '\0';

        /* ----------
         * Correction - precision of dec. number
         * ----------
         */
        Np->Num->post = Np->read_post;

#ifdef DEBUG_TO_FROM_CHAR
        elog(DEBUG_elog_output, "TO_NUMBER (number): '%s'", Np->number);
#endif
        return Np->number;
    }
    else
        return NULL;

    return NULL;
}

/* ----------
 * MACRO: Start part of NUM - for all NUM's to_char variants
 *    (sorry, but I hate copy same code - macro is better..)
 * ----------
 */
#define NUM_TOCHAR_prepare \
do { \
    len = VARSIZE(fmt) - VARHDRSZ;                    \
    if (len <= 0)                            \
        return DirectFunctionCall1(textin, CStringGetDatum(""));    \
    result    = (text *) palloc( (len * NUM_MAX_ITEM_SIZ) + 1 + VARHDRSZ); \
    format    = NUM_cache(len, &Num, VARDATA(fmt), &flag);        \
} while (0)

/* ----------
 * MACRO: Finish part of NUM
 * ----------
 */
#define NUM_TOCHAR_finish \
do { \
    NUM_processor(format, &Num, VARDATA(result),            \
        numstr, plen, sign, TO_CHAR);                \
    pfree(orgnum);                            \
                                    \
    if (flag)                            \
        pfree(format);                        \
                                    \
    /* ----------                            \
     * for result is allocated max memory, which current format-picture\
     * needs, now it must be re-allocate to result real size    \
     * ----------                            \
     */                                \
    if (!(len = strlen(VARDATA(result))))                \
    {                                \
        pfree(result);                        \
        PG_RETURN_NULL();                    \
    }                                \
                                    \
    result_tmp    = result;                    \
    result        = (text *) palloc( len + 1 + VARHDRSZ);        \
                                    \
    strcpy( VARDATA(result), VARDATA(result_tmp));            \
    VARATT_SIZEP(result) = len + VARHDRSZ;                \
    pfree(result_tmp);                        \
} while(0)

/* -------------------
 * NUMERIC to_number() (convert string to numeric)
 * -------------------
 */
Datum
numeric_to_number(PG_FUNCTION_ARGS)
{
    text    *value = PG_GETARG_TEXT_P(0);
    text    *fmt = PG_GETARG_TEXT_P(1);
    NUMDesc    Num;
    Datum    result;
    FormatNode *format;
    char    *numstr;
    int    flag = 0;
    int    len = 0;
    int    scale,
        precision;

    len = VARSIZE(fmt) - VARHDRSZ;

    if (len <= 0)
        PG_RETURN_NULL();

    format = NUM_cache(len, &Num, VARDATA(fmt), &flag);

    numstr = (char *) palloc((len * NUM_MAX_ITEM_SIZ) + 1);

    NUM_processor(format, &Num, VARDATA(value), numstr,
                  VARSIZE(value) - VARHDRSZ, 0, FROM_CHAR);

    scale = Num.post;
    precision = MAX(0, Num.pre) + scale;

    if (flag)
        pfree(format);

    result = DirectFunctionCall3(numeric_in,
             CStringGetDatum(numstr),
             ObjectIdGetDatum(InvalidOid),
             Int32GetDatum(((precision << 16) | scale) + VARHDRSZ));
    pfree(numstr);
    return result;
}

/* ------------------
 * NUMERIC to_char()
 * ------------------
 */
Datum
numeric_to_char(PG_FUNCTION_ARGS)
{
    Numeric    value = PG_GETARG_NUMERIC(0);
    text    *fmt = PG_GETARG_TEXT_P(1);
    NUMDesc    Num;
    FormatNode *format;
    text    *result,
        *result_tmp;
    int    flag = 0;
    int    len = 0,
        plen = 0,
        sign = 0;
    char    *numstr,
        *orgnum,
        *p;
    Numeric    x;

    NUM_TOCHAR_prepare;

    /* ----------
     * On DateType depend part (numeric)
     * ----------
     */
    if (IS_ROMAN(&Num))
    {
        x = DatumGetNumeric(DirectFunctionCall2(numeric_round,
                    NumericGetDatum(value),
                    Int32GetDatum(0)));
        numstr = orgnum =
            int_to_roman(DatumGetInt32(DirectFunctionCall1(numeric_int4,
                                                    NumericGetDatum(x))));
        pfree(x);
    }
    else
    {
        Numeric        val = value;

        if (IS_MULTI(&Num))
        {
            Numeric        a = DatumGetNumeric(DirectFunctionCall1(int4_numeric,
                            Int32GetDatum(10)));
            Numeric        b = DatumGetNumeric(DirectFunctionCall1(int4_numeric,
                            Int32GetDatum(Num.multi)));

            x = DatumGetNumeric(DirectFunctionCall2(numeric_power,
                                                    NumericGetDatum(a),
                                                    NumericGetDatum(b)));
            val = DatumGetNumeric(DirectFunctionCall2(numeric_mul,
                                                      NumericGetDatum(value),
                                                      NumericGetDatum(x)));
            pfree(x);
            pfree(a);
            pfree(b);
            Num.pre += Num.multi;
        }

        x = DatumGetNumeric(DirectFunctionCall2(numeric_round,
                        NumericGetDatum(val),
                        Int32GetDatum(Num.post)));
        orgnum = DatumGetCString(DirectFunctionCall1(numeric_out,
                         NumericGetDatum(x)));
        pfree(x);

        if (*orgnum == '-')
        {                        /* < 0 */
            sign = '-';
            numstr = orgnum + 1;
        }
        else
        {
            sign = '+';
            numstr = orgnum;
        }
        if ((p = strchr(numstr, '.')))
            len = p - numstr;
        else
            len = strlen(numstr);

        if (Num.pre > len)
            plen = Num.pre - len;

        else if (len > Num.pre)
        {
            fill_str(numstr, '#', Num.pre);
            *(numstr + Num.pre) = '.';
            fill_str(numstr + 1 + Num.pre, '#', Num.post);
        }

        if (IS_MULTI(&Num))
            pfree(val);
    }

    NUM_TOCHAR_finish;
    PG_RETURN_TEXT_P(result);
}

/* ---------------
 * INT4 to_char()
 * ---------------
 */
Datum
int4_to_char(PG_FUNCTION_ARGS)
{
    int32    value = PG_GETARG_INT32(0);
    text    *fmt = PG_GETARG_TEXT_P(1);
    NUMDesc    Num;
    FormatNode *format;
    text    *result,
        *result_tmp;
    int    flag = 0;
    int    len = 0,
        plen = 0,
        sign = 0;
    char    *numstr,
        *orgnum;

    NUM_TOCHAR_prepare;

    /* ----------
     * On DateType depend part (int32)
     * ----------
     */
    if (IS_ROMAN(&Num))
    {
        numstr = orgnum = int_to_roman(value);
    }
    else
    {
        if (IS_MULTI(&Num))
        {
            orgnum = DatumGetCString(DirectFunctionCall1(int4out,
                    Int32GetDatum(value * ((int32) pow((double) 10, (double) Num.multi)))));
            Num.pre += Num.multi;
        }
        else
        {
            orgnum = DatumGetCString(DirectFunctionCall1(int4out,
                     Int32GetDatum(value)));
        }
        len = strlen(orgnum);

        if (*orgnum == '-')
        {                        /* < 0 */
            sign = '-';
            --len;
        }
        else
            sign = '+';

        if (Num.post)
        {
            int    i;

            numstr = palloc(len + 1 + Num.post);
            strcpy(numstr, orgnum + (*orgnum == '-' ? 1 : 0));
            *(numstr + len) = '.';

            for (i = len + 1; i <= Num.post + len + 1; i++)
                *(numstr + i) = '0';
            *(numstr + Num.post + len + 1) = '\0';
            pfree(orgnum);
            orgnum = numstr;
        }
        else
            numstr = orgnum + (*orgnum == '-' ? 1 : 0);

        if (Num.pre > len)
            plen = Num.pre - len;
        else if (len > Num.pre)
        {
            fill_str(numstr, '#', Num.pre);
            *(numstr + Num.pre) = '.';
            fill_str(numstr + 1 + Num.pre, '#', Num.post);
        }
    }

    NUM_TOCHAR_finish;
    PG_RETURN_TEXT_P(result);
}

/* ---------------
 * INT8 to_char()
 * ---------------
 */
Datum
int8_to_char(PG_FUNCTION_ARGS)
{
    int64    value = PG_GETARG_INT64(0);
    text    *fmt = PG_GETARG_TEXT_P(1);
    NUMDesc    Num;
    FormatNode *format;
    text    *result,
        *result_tmp;
    int    flag = 0;
    int    len = 0,
        plen = 0,
        sign = 0;
    char    *numstr,
        *orgnum;

    NUM_TOCHAR_prepare;

    /* ----------
     * On DateType depend part (int32)
     * ----------
     */
    if (IS_ROMAN(&Num))
    {
        /* Currently don't support int8 conversion to roman... */
        numstr = orgnum = int_to_roman(DatumGetInt32(
            DirectFunctionCall1(int84, Int64GetDatum(value))));
    }
    else
    {
        if (IS_MULTI(&Num))
        {
            double        multi = pow((double) 10, (double) Num.multi);

            value = DatumGetInt64(DirectFunctionCall2(int8mul,
                          Int64GetDatum(value),
                          DirectFunctionCall1(dtoi8,
                          Float8GetDatum(multi))));
            Num.pre += Num.multi;
        }

        orgnum = DatumGetCString(DirectFunctionCall1(int8out,
                         Int64GetDatum(value)));
        len = strlen(orgnum);

        if (*orgnum == '-')
        {                        /* < 0 */
            sign = '-';
            --len;
        }
        else
            sign = '+';

        if (Num.post)
        {
            int    i;

            numstr = palloc(len + 1 + Num.post);
            strcpy(numstr, orgnum + (*orgnum == '-' ? 1 : 0));
            *(numstr + len) = '.';

            for (i = len + 1; i <= Num.post + len + 1; i++)
                *(numstr + i) = '0';
            *(numstr + Num.post + len + 1) = '\0';
            pfree(orgnum);
            orgnum = numstr;
        }
        else
            numstr = orgnum + (*orgnum == '-' ? 1 : 0);

        if (Num.pre > len)
            plen = Num.pre - len;
        else if (len > Num.pre)
        {
            fill_str(numstr, '#', Num.pre);
            *(numstr + Num.pre) = '.';
            fill_str(numstr + 1 + Num.pre, '#', Num.post);
        }
    }

    NUM_TOCHAR_finish;
    PG_RETURN_TEXT_P(result);
}

/* -----------------
 * FLOAT4 to_char()
 * -----------------
 */
Datum
float4_to_char(PG_FUNCTION_ARGS)
{
    float4    value = PG_GETARG_FLOAT4(0);
    text    *fmt = PG_GETARG_TEXT_P(1);
    NUMDesc    Num;
    FormatNode *format;
    text    *result,
        *result_tmp;
    int    flag = 0;
    int    len = 0,
        plen = 0,
        sign = 0;
    char    *numstr,
        *orgnum,
        *p;

    NUM_TOCHAR_prepare;

    if (IS_ROMAN(&Num))
    {
        numstr = orgnum = int_to_roman((int) rint(value));

    }
    else
    {
        float4        val = value;

        if (IS_MULTI(&Num))
        {
            float    multi = pow((double) 10, (double) Num.multi);

            val = value * multi;
            Num.pre += Num.multi;
        }

        orgnum = (char *) palloc(MAXFLOATWIDTH + 1);
        len = sprintf(orgnum, "%.0f", fabs(val));
        if (Num.pre > len)
            plen = Num.pre - len;
        if (len >= FLT_DIG)
            Num.post = 0;
        else if (Num.post + len > FLT_DIG)
            Num.post = FLT_DIG - len;
        sprintf(orgnum, "%.*f", Num.post, val);

        if (*orgnum == '-')
        {                        /* < 0 */
            sign = '-';
            numstr = orgnum + 1;
        }
        else
        {
            sign = '+';
            numstr = orgnum;
        }
        if ((p = strchr(numstr, '.')))
            len = p - numstr;
        else
            len = strlen(numstr);

        if (Num.pre > len)
            plen = Num.pre - len;

        else if (len > Num.pre)
        {
            fill_str(numstr, '#', Num.pre);
            *(numstr + Num.pre) = '.';
            fill_str(numstr + 1 + Num.pre, '#', Num.post);
        }
    }

    NUM_TOCHAR_finish;
    PG_RETURN_TEXT_P(result);
}

/* -----------------
 * FLOAT8 to_char()
 * -----------------
 */
Datum
float8_to_char(PG_FUNCTION_ARGS)
{
    float8    value = PG_GETARG_FLOAT8(0);
    text    *fmt = PG_GETARG_TEXT_P(1);
    NUMDesc    Num;
    FormatNode *format;
    text    *result,
        *result_tmp;
    int    flag = 0;
    int    len = 0,
        plen = 0,
        sign = 0;
    char    *numstr,
        *orgnum,
        *p;

    NUM_TOCHAR_prepare;

    if (IS_ROMAN(&Num))
    {
        numstr = orgnum = int_to_roman((int) rint(value));

    }
    else
    {
        float8        val = value;

        if (IS_MULTI(&Num))
        {
            double    multi = pow((double) 10, (double) Num.multi);

            val = value * multi;
            Num.pre += Num.multi;
        }
        orgnum = (char *) palloc(MAXDOUBLEWIDTH + 1);
        len = sprintf(orgnum, "%.0f", fabs(val));
        if (Num.pre > len)
            plen = Num.pre - len;
        if (len >= DBL_DIG)
            Num.post = 0;
        else if (Num.post + len > DBL_DIG)
            Num.post = DBL_DIG - len;
        sprintf(orgnum, "%.*f", Num.post, val);

        if (*orgnum == '-')
        {                        /* < 0 */
            sign = '-';
            numstr = orgnum + 1;
        }
        else
        {
            sign = '+';
            numstr = orgnum;
        }
        if ((p = strchr(numstr, '.')))
            len = p - numstr;
        else
            len = strlen(numstr);

        if (Num.pre > len)
            plen = Num.pre - len;

        else if (len > Num.pre)
        {
            fill_str(numstr, '#', Num.pre);
            *(numstr + Num.pre) = '.';
            fill_str(numstr + 1 + Num.pre, '#', Num.post);
        }
    }

    NUM_TOCHAR_finish;
    PG_RETURN_TEXT_P(result);
}

pgsql-patches by date:

Previous
From: Bruce Momjian
Date:
Subject: Re: Re: 7.0.3 to_char() (was: [BUGS] Bugs in to_char function)
Next
From: Karel Zak
Date:
Subject: Re: Re: 7.0.3 to_char() (was: [BUGS] Bugs in to_char function)