]> granicus.if.org Git - postgresql/commitdiff
I finish devel. of Oracle compatible DateTime routines TO_CHAR(),
authorBruce Momjian <bruce@momjian.us>
Mon, 29 Nov 1999 23:26:18 +0000 (23:26 +0000)
committerBruce Momjian <bruce@momjian.us>
Mon, 29 Nov 1999 23:26:18 +0000 (23:26 +0000)
TO_DATE()
and PgSQL extension FROM_CHAR().

TO_CHAR() routine allow formating text output with a datetime values:

        SELECT TO_CHAR('now'::datetime, '"Now is: "HH24:MI:SS');
        to_char
        ----------------
        Now is: 21:04:10

FROM_CHAR() routine allow convert text to a datetime:

        SELECT FROM_CHAR('September 1999 10:20:30', 'FMMonth YYYY
HH:MI:SS');
        from_char
        -----------------------------
        Wed Sep 01 10:20:30 1999 CEST

TO_DATE() is equal with FROM_CHAR(), but output a Date only:

        SELECT TO_DATE('September 1999 10:20:30', 'FMMonth YYYY
HH:MI:SS');
        to_date
        ----------
        09-01-1999

In attache is compressed dir for the contrib. All is prepared, but I'am
not
sure if Makefile is good (probably yes).

Comments & suggestions ?

Thomas, thank you for your good advices.

                                                        Karel

------------------------------------------------------------------------------

Karel Zak <zakkr@zf.jcu.cz>
http://home.zf.jcu.cz/~zakkr/

contrib/README
contrib/dateformat/Makefile [new file with mode: 0644]
contrib/dateformat/test/Makefile [new file with mode: 0644]
contrib/dateformat/test/README [new file with mode: 0644]
contrib/dateformat/test/rand_datetime.c [new file with mode: 0644]
contrib/dateformat/test/regress.sql [new file with mode: 0644]
contrib/dateformat/to-from_char.c [new file with mode: 0644]
contrib/dateformat/to-from_char.doc [new file with mode: 0644]
contrib/dateformat/to-from_char.h [new file with mode: 0644]
contrib/dateformat/to-from_char.sql.in [new file with mode: 0644]

index a59880c2653e74748dfb33a7ca8bde4e1af6f2f5..e280186cb37de3c9316347ca9a5ac019e0e016a3 100644 (file)
@@ -10,6 +10,14 @@ array -
        Array iterator functions
        by Massimo Dal Zotto <dz@cs.unitn.it>
 
+bit -
+       Bit type
+       by Adriaan Joubert <a.joubert@albourne.com>
+
+dateformat -
+       Date Formatting to/from character strings
+       by Karel Zak - Zakkr <zakkr@zf.jcu.cz>
+
 datetime -
        Date & time functions
        by Massimo Dal Zotto <dz@cs.unitn.it>
diff --git a/contrib/dateformat/Makefile b/contrib/dateformat/Makefile
new file mode 100644 (file)
index 0000000..849d7a7
--- /dev/null
@@ -0,0 +1,71 @@
+#-------------------------------------------------------------------------
+#
+# Makefile --
+#
+#    Makefile for TO-FROM_CHAR module.
+#
+#-------------------------------------------------------------------------
+
+PGDIR = ../..
+SRCDIR = $(PGDIR)/src
+
+include $(SRCDIR)/Makefile.global
+
+INCLUDE_OPT =  -I ./ \
+               -I $(SRCDIR)/ \
+               -I $(SRCDIR)/include \
+               -I $(SRCDIR)/port/$(PORTNAME)
+
+CFLAGS += $(INCLUDE_OPT) $(CFLAGS_SL)
+
+MODNAME =      to-from_char
+
+SQLDEFS =      $(MODNAME).sql
+
+MODULE =       $(MODNAME)$(DLSUFFIX)
+
+MODDIR =       $(LIBDIR)/modules
+
+SQLDIR =       $(LIBDIR)/sql
+
+all:           module sql
+
+module:                $(MODULE)
+
+sql:           $(SQLDEFS)
+
+install:       $(MODULE) $(SQLDEFS) $(MODDIR) $(SQLDIR)
+               cp -p $(MODULE) $(MODDIR)/
+               strip $(MODDIR)/$(MODULE)
+               cp -p $(SQLDEFS) $(SQLDIR)/
+
+install-doc:   
+               if [ -d "$(DOCDIR)" ]; then \
+                   cp -p *.doc $(DOCDIR); \
+               else \
+                   cp -p *.doc $(SQLDIR); \
+               fi
+
+$(MODDIR):
+               mkdir -p $@
+
+$(SQLDIR):
+               mkdir -p $@
+
+%.sql: %.sql.in
+               sed "s|MODULE_PATHNAME|$(MODDIR)/$(MODULE)|" < $< > $@
+
+.SUFFIXES: $(DLSUFFIX)
+
+%$(DLSUFFIX): %.c
+               $(CC) $(CFLAGS) -shared -o $@ $<
+
+depend dep:
+               $(CC) -MM $(INCLUDE_OPT) *.c >depend
+
+clean:
+               rm -f *~ $(MODULE) $(MODNAME).sql
+
+ifeq (depend,$(wildcard depend))
+include depend
+endif
diff --git a/contrib/dateformat/test/Makefile b/contrib/dateformat/test/Makefile
new file mode 100644 (file)
index 0000000..6eb539c
--- /dev/null
@@ -0,0 +1,25 @@
+
+PROGRAM        = rand_datetime
+       
+OBJECTS        = rand_datetime.o
+
+CFLAGS         = -Wall -fpic -O3
+CC     = gcc
+RM     = rm -f
+LIBS   =
+INCLUDE        =
+
+COMPILE = $(CC) $(CPPFLAGS) $(CFLAGS) $(INCLUDE)
+LINK    = $(CC) $(CFLAGS) -o $@ $(LIBS)
+
+
+all: $(PROGRAM)        
+
+$(PROGRAM): $(OBJECTS)
+       $(LINK) $(OBJECTS)
+
+.c.o: $<
+       $(COMPILE) -c $<
+
+clean:
+       $(RM) -f *~ $(OBJECTS) $(PROGRAM)
diff --git a/contrib/dateformat/test/README b/contrib/dateformat/test/README
new file mode 100644 (file)
index 0000000..63177f2
--- /dev/null
@@ -0,0 +1,33 @@
+
+       TO/FROM CHAR tests
+       ~~~~~~~~~~~~~~~~~~
+
+ * rand_datetime
+
+       The program 'rand_datetime' output a random datetime strings 
+       (with yaer range 0..9999), you can use this for datetime testing.
+
+       You can usage this (example) for table filling.
+
+       Usage:
+       
+       ./rand_datetime <randfile> <num> <prefix> <postfix>
+       
+       Example:
+       
+       ./rand_datetime /dev/urandom 2 "INSERT INTO tab VALUES('" "'::datetime);"
+
+       INSERT INTO tab VALUES('Sat 27 Jul 13:08:57 19618'::datetime);
+       INSERT INTO tab VALUES('Wed 25 Aug 20:31:50 27450'::datetime);  
+
+ * regress
+       
+       psql < regress.sql      (all answers, must be TRUE, for Posgres
+                                 datestyle)
+       
+       
+       --> TO_DATE() is simular as FROM_CHAR(), but convert full datetime
+           to date ==> needn't test (?).
+
+
diff --git a/contrib/dateformat/test/rand_datetime.c b/contrib/dateformat/test/rand_datetime.c
new file mode 100644 (file)
index 0000000..6a96776
--- /dev/null
@@ -0,0 +1,71 @@
+
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdlib.h>
+
+
+char   *month[]        = {
+       "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec",NULL        
+};
+
+char   *day[] = { "Sun","Mon","Tue","Wed","Thu","Fri","Sat", NULL };
+
+int num(FILE *f, int min, int max)
+{
+       int     x, y, one;
+       
+       one = x = fgetc(f);
+       
+       
+       if (x < min)
+               x = min;        
+       else if (x > max) {
+               while(x > max)
+                       x /= 2;  
+               return x;
+       }
+       
+       do {
+               y = fgetc(f);
+               if ((x+y) > max)
+                       return x;
+               x += y;
+       } while(--one > 0);
+       
+       return x;       
+}
+
+int main(int argc, char **argv)
+{
+       FILE    *f; 
+       int     count;
+       
+       if (argc < 5) {
+               printf("\nUsage: %s <randfile> <num> <prefix> <postfix>\n", argv[0]);
+               printf("\n(C) Karel Zak - Zakkr 1999\n\n");
+               exit(1);
+       }
+       
+       if ((f = fopen(argv[1], "r")) == NULL) {
+               perror(argv[1]);
+               exit(1);
+       }
+
+       count = atoi(argv[2]);
+       
+       for(; count > 0; --count) {
+               fprintf(stdout, "%s%s %02d %s %02d:%02d:%02d %d%s\n",
+                       argv[3],
+                       day[ num(f, 0, 6) ],
+                       num(f, 1, 28),
+                       month[ num(f, 0, 11) ], 
+                       num(f, 0, 23),
+                       num(f, 0, 59),
+                       num(f, 0, 59),
+                       num(f, 0, 9999),
+                       argv[4]
+               );      
+       }       
+       exit(0);
+}
\ No newline at end of file
diff --git a/contrib/dateformat/test/regress.sql b/contrib/dateformat/test/regress.sql
new file mode 100644 (file)
index 0000000..f3c2815
--- /dev/null
@@ -0,0 +1,58 @@
+
+---
+--- Postgres DateStyle needs all tests which parsing 'now'::datetime string
+---
+SET DATESTYLE TO 'Postgres';
+
+
+SELECT 'now'::datetime = 
+       TO_CHAR('now'::datetime, 'Dy Mon DD HH24:MI:SS YYYY')::datetime 
+as "Now vs. to_char";  
+
+       
+SELECT 'now'::datetime = 
+       FROM_CHAR('now'::datetime, 'Dy Mon DD HH24:MI:SS YYYY')
+as "Now vs. from_char";
+
+
+SELECT FROM_CHAR('now'::datetime, 'Dy Mon DD HH24:MI:SS YYYY') = 
+       TO_CHAR('now'::datetime, 'Dy Mon DD HH24:MI:SS YYYY')::datetime 
+as "From_char vs. To_char";    
+       
+
+SELECT 'now'::datetime =       
+       FROM_CHAR( 
+               TO_CHAR('now'::datetime, '"Time: "HH24-MI-SS" Date: "Dy DD Mon YYYY'),
+               '"Time: "HH24-MI-SS" Date: "Dy DD Mon YYYY'
+       )
+as "High from/to char test";   
+
+
+SELECT TO_CHAR('now'::datetime, 'SSSS')::int = 
+               TO_CHAR('now'::datetime, 'HH24')::int * 3600 + 
+               TO_CHAR('now'::datetime, 'MI')::int * 60 + 
+               TO_CHAR('now'::datetime, 'SS')::int
+as "SSSS test";
+
+
+SELECT TO_CHAR('now'::datetime, 'WW')::int =
+               (TO_CHAR('now'::datetime, 'DDD')::int -
+               TO_CHAR('now'::datetime, 'D')::int + 7) / 7 
+as "Week test";        
+        
+
+SELECT TO_CHAR('now'::datetime, 'Q')::int =
+               TO_CHAR('now'::datetime, 'MM')::int / 3 + 1
+as "Quartal test";
+
+
+SELECT TO_CHAR('now'::datetime, 'DDD')::int =
+       (TO_CHAR('now'::datetime, 'WW')::int * 7) -  
+       (7 - TO_CHAR('now'::datetime, 'D')::int) +
+       (7 - TO_CHAR(('01-Jan-'|| 
+               TO_CHAR('now'::datetime,'YYYY'))::datetime,'D')::int)
+       +1
+as "Week and day test";
+
+
+
diff --git a/contrib/dateformat/to-from_char.c b/contrib/dateformat/to-from_char.c
new file mode 100644 (file)
index 0000000..eebd7a8
--- /dev/null
@@ -0,0 +1,1382 @@
+
+/******************************************************************
+ *
+ *   The PostgreSQL modul for DateTime formating, inspire with
+ *   Oracle TO_CHAR() / TO_DATE() routines.  
+ *
+ *   Copyright (c) 1999, Karel Zak "Zakkr" <zakkr@zf.jcu.cz>
+ *
+ *   This file is distributed under the GNU General Public License
+ *   either version 2, or (at your option) any later version.
+ *
+ *
+ *   NOTE:
+ *     In this modul is _not_ used POSIX 'struct tm' type, but 
+ *     PgSQL type, which has tm_mon based on one (_non_ zero) and
+ *     year not based on 1900, but is used full year number.  
+ *     Modul support AC / BC years.     
+ *
+ ******************************************************************/
+
+/*
+#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 "postgres.h"
+#include "utils/builtins.h"
+           
+#include "to-from_char.h"
+
+#define MAX_NODE_SIZ           16      /* maximal length of one node */
+
+#ifdef DEBUG_TO_FROM_CHAR
+       #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
+
+/*------ 
+ * (External) defined in PgSQL dt.c (datetime utils) 
+ *------
+ */
+extern  char           *months[],              /* month abbreviation   */
+                       *days[];                /* full days            */
+
+/*------
+ * Private definitions
+ *------
+ */
+static struct tm       _tm, *tm = &_tm;
+
+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         " BC"
+
+/*------ 
+ * Months in roman-numeral 
+ * (Must be conversely for seq_search (in FROM_CHAR), because 
+ *  'VIII' must be over 'V')   
+ *------
+ */
+static char *rm_months[] = {
+       "XII", "XI", "X", "IX", "VIII", "VII",
+       "VI", "V", "IV", "III", "II", "I", NULL
+};
+
+/*------ 
+ * Ordinal postfixes 
+ *------
+ */
+static char *numTH[] = { "ST", "ND", "RD", "TH", NULL };
+static char *numth[] = { "st", "nd", "rd", "th", NULL };
+
+/*------ 
+ * Flags: 
+ *------
+ */
+#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
+
+/****************************************************************************
+ *                         Structs for format parsing 
+ ****************************************************************************/
+
+/*------
+ * 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          0
+#define        NODE_TYPE_ACTION        1
+#define NODE_TYPE_CHAR         2
+#define NODE_LAST              3       /* internal option      */
+
+#define SUFFTYPE_PREFIX                1
+#define SUFFTYPE_POSTFIX       2
+
+
+/*****************************************************************************
+ *                     KeyWords definition & action 
+ *****************************************************************************/
+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 TO / FROM CHAR
+ *------
+ */
+static KeySuffix suff[] = {
+       {       "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                }      
+};
+
+/*------
+ *
+ * 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 I_poz (!)
+ *
+ * For fast search is used the KWindex[256], in this index is DCH_ enums for
+ * each ASCII position or -1 if char is not used in the KeyWord. Search example 
+ * for string "MM":
+ *     1)      see KWindex to KWindex[77] ('M'), 
+ *     2)      take keywords position from KWindex[77]
+ *     3)      run sequential search in keywords[] from position   
+ *
+ *------
+ */
+
+typedef enum { 
+       DCH_CC,
+       DCH_DAY,
+       DCH_DDD,
+       DCH_DD,
+       DCH_DY,
+       DCH_Day,
+       DCH_Dy,
+       DCH_D,
+       DCH_HH24,
+       DCH_HH12,
+       DCH_HH,
+       DCH_J,
+       DCH_MI,
+       DCH_MM,
+       DCH_MONTH,
+       DCH_MON,
+       DCH_Month,
+       DCH_Mon,
+       DCH_Q,
+       DCH_RM,
+       DCH_SSSS,
+       DCH_SS,
+       DCH_WW,
+       DCH_W,
+       DCH_Y_YYY,
+       DCH_YYYY,
+       DCH_YYY,
+       DCH_YY,
+       DCH_Y,
+       DCH_day,
+       DCH_dy,
+       DCH_month,
+       DCH_mon,
+       /* last */
+       _DCH_last_
+} I_poz;
+
+static KeyWord keywords[] = {  
+/*     keyword,        len, func.              I_poz        is in KWindex */
+                                                       
+{      "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           },      
+{      "HH24",         4, dch_time,    DCH_HH24        },      /*H*/
+{      "HH12",         4, dch_time,    DCH_HH12        },
+{      "HH",           2, dch_time,    DCH_HH          },
+{      "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         },
+{      "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          },
+{      "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           },
+{      "day",          3, dch_date,    DCH_day         },      /*d*/
+{      "dy",           2, dch_date,    DCH_dy          },      
+{      "month",        5, dch_date,    DCH_month       },      /*m*/   
+{      "mon",          3, dch_date,    DCH_mon         },
+/* last */
+{      NULL,           0, NULL,        0               }};
+
+
+static int KWindex[256] = {
+/*
+0      1       2       3       4       5       6       7       8       9
+*/
+-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,     -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,     -1,     DCH_CC, DCH_DAY,-1,     
+-1,    -1,     DCH_HH24,-1,    DCH_J,  -1,     -1,     DCH_MI, -1,     -1,
+-1,    DCH_Q,  DCH_RM, DCH_SSSS,-1,    -1,     -1,     DCH_WW, -1,     DCH_Y_YYY,
+-1,    -1,     -1,     -1,     -1,     -1,     -1,     -1,     -1,     -1,
+DCH_day,-1,    -1,     -1,     -1,     -1,     -1,     -1,     -1,     DCH_month,      
+-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,     -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,     -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,     -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,     -1,     -1,     -1,     -1,     -1,     -1,     -1,     -1,
+-1,    -1,     -1,     -1,     -1,     -1
+};     
+
+
+/*------
+ * Fast sequential search, use index for selection data 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 ( (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;
+}
+
+/*------
+ * Format parser, search small keywords and keyword's suffixes, and make 
+ * format-node tree. 
+ *------
+ */
+#undef FUNC_NAME
+#define FUNC_NAME      "parse_format"
+
+static void parse_format(FormatNode *node, char *str, KeyWord *kw, 
+                        KeySuffix *suf, int *index)
+{
+       KeySuffix       *s;
+       FormatNode      *n;
+       int             node_set=0,
+                       suffix,
+                       last=0;
+       n = node;
+
+       while(*str) {
+               suffix=0;
+               
+               /* prefix */
+               if ((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;
+                       
+                       /* postfix */
+                       if (*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 != '\\') {
+                               while(*(++str)) {
+                                       if (*str == '"') {
+                                               str++;
+                                               break;
+                                       }
+                                       n->type = NODE_TYPE_CHAR;
+                                       n->character = *str;
+                                       n->key = (KeyWord *) NULL;
+                                       n->suffix = 0;
+                                       ++n;
+                               }
+                               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 *node_action(FormatNode *node, char *inout, int flag)
+{
+       FormatNode      *n;
+       char            *s;
+       
+       for(n=node, s=inout; n->type != NODE_TYPE_END; n++, s++) {
+               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 {
+               
+                       /*
+                        * 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)) {
+                                       while(*s != '\0' && isspace(*(s+1)))
+                                               ++s;    
+                               }
+                       }
+               }       
+       }
+       
+       if (flag == TO_CHAR)
+               *s = '\0';
+       return inout;
+}
+
+/*****************************************************************************
+ *                     Private utils 
+ *****************************************************************************/
+
+/*------
+ * Return ST/ND/RD/TH for simple (1..9) numbers 
+ * type --> 0 upper, 1 lower
+ *------       
+ */    
+static char *get_th(int num, int type)
+{
+       switch(num) {
+               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];                
+       }
+       if (type==TH_UPPER) return numTH[3];
+       return numth[3];
+}
+
+/*------
+ * Convert string-number to ordinal string-number 
+ * type --> 0 upper, 1 lower   
+ *------
+ */
+#undef         FUNC_NAME
+#define        FUNC_NAME       "str_numth"
+
+static char *str_numth(char *dest, char *src, int type)
+{
+       int     len = strlen(src),
+               num=0, f_num=0; 
+       
+       num = *(src+(len-1));
+       if (num < 48 || num > 57)
+               elog(ERROR, "%s: in '%s' is not number.", FUNC_NAME, src);
+       
+       num -= 48;
+       if (num==1 || num==2) {                 /* 11 || 12 */
+               f_num = atoi(src);
+               if (abs(f_num)==11 || abs(f_num)==12)
+                       num=0;
+       }
+       sprintf(dest, "%s%s", src, get_th(num, type));
+       return dest; 
+}
+
+/*------
+ * Return length of integer writed in string 
+ *-------
+ */
+static int int4len(int4 num)
+{
+       char b[16];
+       
+       sprintf(b, "%d", num);
+       return strlen(b);
+}
+
+/*------
+ * 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;
+}
+
+/*------
+ * Check if in string is AC or BC (return: 0==none; -1==BC; 1==AC)  
+ *------
+ */
+static int is_acdc(char *str, int *len)
+{
+       char    *p;
+       
+       for(p=str; *p != '\0'; p++) {
+               if (isspace(*p))
+                       continue;
+                       
+               if (*(p+1)) { 
+                       if (toupper(*p)=='B' && toupper(*(++p))=='C') {
+                               *len += (p - str) +1;
+                               return -1;      
+                       } else if (toupper(*p)=='A' && toupper(*(++p))=='C') {
+                               *len += (p - str) +1;
+                               return 1;       
+                       }
+               } 
+               return 0;       
+       }
+       return 0;
+} 
+
+/*------
+ * 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
+/*-------
+ * Call for debug and for KWindex checking; (Show ASCII char and defined 
+ * keyword for each used position  
+ *-------
+ */    
+static void dump_KWindex()
+{
+       int     i;
+       
+       for(i=0; i<255; i++) {
+               if (KWindex[i] != -1)
+                       elog(NOTICE, "%c: %s, ", i, keywords[ KWindex[i] ].name);
+       }               
+}
+#endif
+
+/*****************************************************************************
+ *                             Master routines  
+ *****************************************************************************/
+
+/*
+ * Spip TM / th in FROM_CHAR
+ */
+#define SKIP_THth(_suf)                (S_THth(_suf) ? 2 : 0)   
+
+/*------
+ * Master of TIME for TO_CHAR   - write (inout) formated string
+ *                    FROM_CHAR - scan (inout) string by course of FormatNode 
+ *------
+ */
+#undef FUNC_NAME
+#define FUNC_NAME      "dch_time"
+
+static int dch_time(int arg, char *inout, int suf, int flag, FormatNode *node) 
+{
+       char    *p_inout = inout;
+
+       switch(arg) {
+       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", &tm->tm_hour);
+                               return int4len((int4) tm->tm_hour)-1 + SKIP_THth(suf);
+                       } else {
+                               sscanf(inout, "%02d", &tm->tm_hour);
+                               return 1 + SKIP_THth(suf);
+                       }
+                               
+               }
+       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", &tm->tm_hour);
+                               return int4len((int4) tm->tm_hour)-1 + SKIP_THth(suf);
+                       } else {
+                               sscanf(inout, "%02d", &tm->tm_hour);
+                               return 1 + SKIP_THth(suf);
+                       }
+               }
+       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", &tm->tm_min);
+                               return int4len((int4) tm->tm_min)-1 + SKIP_THth(suf);
+                       } else {
+                               sscanf(inout, "%02d", &tm->tm_min);
+                               return 1 + SKIP_THth(suf);
+                       }
+               }
+       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", &tm->tm_sec);
+                               return int4len((int4) tm->tm_sec)-1 + SKIP_THth(suf);
+                       } else {
+                               sscanf(inout, "%02d", &tm->tm_sec);
+                               return 1 + SKIP_THth(suf);
+                       }
+               }
+       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) 
+                       elog(ERROR, "%s: SSSS is not supported", FUNC_NAME);
+       }       
+       return 0;       
+}
+
+#define CHECK_SEQ_SEARCH(_l, _s) {                                     \
+       if (_l <= 0) {                                                  \
+               elog(ERROR, "%s: bad value for %s", FUNC_NAME, _s);     \
+       }                                                               \
+}
+
+/*------
+ * Master of DATE for TO_CHAR   - write (inout) formated string
+ *                    FROM_CHAR - scan (inout) string by course of FormatNode 
+ *------
+ */
+#undef FUNC_NAME
+#define FUNC_NAME      "dch_date"
+
+static int dch_date(int arg, char *inout, int suf, int flag, FormatNode *node)
+{
+       char    buff[MAX_NODE_SIZ],             
+               *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 one-upper.    
+        * This convention is used for MONTH, MON, DAY, DY
+        *------
+        */
+       if (flag == FROM_CHAR) {
+               if (arg == DCH_MONTH || arg == DCH_Month || arg == DCH_month) {
+       
+                       tm->tm_mon = seq_search(inout, months_full, ONE_UPPER, FULL_SIZ, &len);
+                       CHECK_SEQ_SEARCH(len, "MONTH/Month/month");
+                       ++tm->tm_mon;
+                       if (S_FM(suf))  return len-1;
+                       else            return 8;
+
+               } else if (arg == DCH_MON || arg == DCH_Mon || arg == DCH_mon) {
+               
+                       tm->tm_mon = seq_search(inout, months, ONE_UPPER, MAX_MON_LEN, &len);
+                       CHECK_SEQ_SEARCH(len, "MON/Mon/mon");
+                       ++tm->tm_mon;
+                       return 2;
+               
+               } else if (arg == DCH_DAY || arg == DCH_Day || arg == DCH_day) {
+               
+                       tm->tm_wday =  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) {
+                       
+                       tm->tm_wday =  seq_search(inout, days, ONE_UPPER, MAX_DY_LEN, &len);
+                       CHECK_SEQ_SEARCH(len, "DY/Dy/dy");
+                       return 2;
+                       
+               } 
+       } 
+       
+       switch(arg) {
+       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", &tm->tm_mon);
+                               return int4len((int4) tm->tm_mon)-1 + SKIP_THth(suf);
+                       } else {
+                               sscanf(inout, "%02d", &tm->tm_mon);
+                               return 1 + SKIP_THth(suf);
+                       }               
+               }       
+       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", &tm->tm_yday);
+                               return int4len((int4) tm->tm_yday)-1 + SKIP_THth(suf);
+                       } else {
+                               sscanf(inout, "%03d", &tm->tm_yday);
+                               return 2 + SKIP_THth(suf);
+                       }       
+               }
+       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", &tm->tm_mday);
+                               return int4len((int4) tm->tm_mday)-1 + SKIP_THth(suf);
+                       } else {
+                               sscanf(inout, "%02d", &tm->tm_mday);
+                               return 1 + SKIP_THth(suf);
+                       }       
+               }       
+       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));
+                       if (S_THth(suf)) 
+                               return 2;
+                       return 0;
+               } else if (flag == FROM_CHAR) { 
+                       sscanf(inout, "%1d", &tm->tm_wday);
+                       if(tm->tm_wday) --tm->tm_wday;
+                       return 0 + SKIP_THth(suf);
+               } 
+       case DCH_WW:
+               if (flag == TO_CHAR) {
+                       sprintf(inout, "%0*d", S_FM(suf) ? 0 : 2,  
+                               (tm->tm_yday - tm->tm_wday + 7) / 7);           
+                       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)
+                       elog(ERROR, "%s: WW is not supported", FUNC_NAME);
+       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));
+                       if (S_THth(suf)) 
+                               return 2;
+                       return 0;
+               } else if (flag == FROM_CHAR)
+                       elog(ERROR, "%s: Q is not supported", FUNC_NAME);
+       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)
+                       elog(ERROR, "%s: CC is not supported", FUNC_NAME);
+       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));
+                       if (tm->tm_year < 0)
+                               strcat(inout, BC_STR);  
+                       return strlen(p_inout)-1;
+               } else if (flag == FROM_CHAR) {
+                       int     cc, yy;
+                       sscanf(inout, "%d,%03d", &cc, &yy);
+                       tm->tm_year = (cc * 1000) + yy;
+                       
+                       if (!S_FM(suf) && tm->tm_year <= 9999 && tm->tm_year >= -9999)  
+                               len = 5; 
+                       else                                    
+                               len = int4len((int4) tm->tm_year)+1;
+                       len +=  SKIP_THth(suf); 
+                       /* AC/BC */     
+                       if (is_acdc(inout+len, &len) < 0 && tm->tm_year > 0) 
+                               tm->tm_year = -(tm->tm_year);
+                       if (tm->tm_year < 0) 
+                               tm->tm_year = tm->tm_year+1; 
+                       return len-1;
+               }       
+       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));
+                       if (tm->tm_year < 0)
+                               strcat(inout, BC_STR);
+                       return strlen(p_inout)-1;
+               } else if (flag == FROM_CHAR) {
+                       sscanf(inout, "%d", &tm->tm_year);
+                       if (!S_FM(suf) && tm->tm_year <= 9999 && tm->tm_year >= -9999)  
+                               len = 4;
+                       else                                    
+                               len = int4len((int4) tm->tm_year);
+                       len +=  SKIP_THth(suf);
+                       /* AC/BC */     
+                       if (is_acdc(inout+len, &len) < 0 && tm->tm_year > 0) 
+                               tm->tm_year = -(tm->tm_year);
+                       if (tm->tm_year < 0) 
+                               tm->tm_year = tm->tm_year+1; 
+                       return len-1;
+               }       
+       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));
+                       if (S_THth(suf)) return 4;
+                       return 2;
+               } else if (flag == FROM_CHAR) {
+                       int     yy;
+                       sscanf(inout, "%03d", &yy);
+                       tm->tm_year = (tm->tm_year/1000)*1000 +yy;
+                       return 2 +  SKIP_THth(suf);
+               }       
+       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));
+                       if (S_THth(suf)) return 3;
+                       return 1;
+               } else if (flag == FROM_CHAR) {
+                       int     yy;
+                       sscanf(inout, "%02d", &yy);
+                       tm->tm_year = (tm->tm_year/100)*100 +yy;
+                       return 1 +  SKIP_THth(suf);
+               }       
+       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));
+                       if (S_THth(suf)) return 2;
+                       return 0;
+               } else if (flag == FROM_CHAR) {
+                       int     yy;
+                       sscanf(inout, "%1d", &yy);
+                       tm->tm_year = (tm->tm_year/10)*10 +yy;
+                       return 0 +  SKIP_THth(suf);
+               }       
+       case DCH_RM:
+               if (flag == TO_CHAR) {
+                       sprintf(inout, "%*s", S_FM(suf) ? 0 : -4,   
+                               rm_months[ 12 - tm->tm_mon ]);
+                       if (S_FM(suf)) return strlen(p_inout)-1;
+                       else return 3;
+               } else if (flag == FROM_CHAR) {
+                       tm->tm_mon = 11-seq_search(inout, rm_months, ALL_UPPER, FULL_SIZ, &len);
+                       CHECK_SEQ_SEARCH(len, "RM");
+                       ++tm->tm_mon;
+                       if (S_FM(suf))  return len-1;
+                       else            return 3;
+               }       
+       case DCH_W:
+               if (flag == TO_CHAR) {
+                       sprintf(inout, "%d", (tm->tm_mday - tm->tm_wday +7) / 7 );
+                       if (S_THth(suf)) 
+                               str_numth(p_inout, inout, S_TH_TYPE(suf));
+                       if (S_THth(suf)) return 2;
+                       return 0;
+               } else if (flag == FROM_CHAR)
+                       elog(ERROR, "%s: W is not supported", FUNC_NAME);
+       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)
+                       elog(ERROR, "%s: J is not supported", FUNC_NAME);       
+       }       
+       return 0;       
+}
+
+/****************************************************************************
+ *                             Public routines
+ ***************************************************************************/
+
+       
+/********************************************************************* 
+ *
+ * to_char
+ *
+ * Syntax:
+ *
+ *     text *to_char(DateTime *dt, text *fmt)
+ *
+ * Purpose:
+ *
+ *     Returns string, with date and/or time, formated at 
+ *      argument 'fmt'                  
+ *
+ *********************************************************************/
+
+#undef FUNC_NAME
+#define FUNC_NAME      "to_char"
+       
+text 
+*to_char(DateTime *dt, text *fmt)
+{
+       FormatNode              *tree;
+       text                    *result;
+       char                    *pars_str;
+       double                  fsec;
+       char                    *tzn;
+       int                     len=0, tz;
+
+       if ((!PointerIsValid(dt)) || (!PointerIsValid(fmt)))
+               return NULL;
+       
+       len     = VARSIZE(fmt) - VARHDRSZ; 
+       
+       if (!len) 
+               return textin("");
+
+       tm->tm_sec      =0;     tm->tm_year     =0;
+       tm->tm_min      =0;     tm->tm_wday     =0;
+       tm->tm_hour     =0;     tm->tm_yday     =0;
+       tm->tm_mday     =1;     tm->tm_isdst    =0;
+       tm->tm_mon      =1;
+
+       if (DATETIME_IS_EPOCH(*dt))
+       {
+               datetime2tm(SetDateTime(*dt), NULL, tm, &fsec, NULL);
+       } else if (DATETIME_IS_CURRENT(*dt)) {
+               datetime2tm(SetDateTime(*dt), &tz, tm, &fsec, &tzn);
+       } else {
+               if (datetime2tm(*dt, &tz, tm, &fsec, &tzn) != 0)
+                       elog(ERROR, "s%: Unable to convert datetime to tm", FUNC_NAME);
+       }
+
+       /* In dt.c is j2day as static :-(((
+               tm->tm_wday = j2day(date2j(tm->tm_year, tm->tm_mon, tm->tm_mday)); 
+          must j2day convert itself... 
+        */
+        
+       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;
+       
+       tree    = (FormatNode *) palloc(len * sizeof(FormatNode) +1);   
+       result  = (text *) palloc( len * MAX_NODE_SIZ + VARHDRSZ);      
+       (tree + len)->type = NODE_LAST;   
+       
+       pars_str = VARDATA(fmt);
+       pars_str[ len ] = '\0';
+
+       parse_format( tree, pars_str, keywords, suff, KWindex);
+
+       node_action(tree, VARDATA(result), TO_CHAR);
+       VARSIZE(result) = strlen(VARDATA(result)) + VARHDRSZ; 
+       
+       return result;
+}
+
+
+/********************************************************************* 
+ *
+ * from_char
+ *
+ * Syntax:
+ *
+ *     DateTime *from_char(text *date_str, text *fmt)
+ *
+ * Purpose:
+ *
+ *     Make DateTime from date_str which is formated at argument 'fmt'          
+ *     ( from_char is reverse to_char() )
+ *
+ *********************************************************************/
+
+#undef FUNC_NAME
+#define FUNC_NAME      "from_char"
+       
+DateTime 
+*from_char(text *date_str, text *fmt)
+{
+       FormatNode              *tree;
+       DateTime                *result;
+       char                    *pars_str;
+       int                     len=0,
+                               fsec=0,
+                               tz=0;
+
+       if ((!PointerIsValid(date_str)) || (!PointerIsValid(fmt)))
+               return NULL;
+       
+       tm->tm_sec      =0;     tm->tm_year     =0;
+       tm->tm_min      =0;     tm->tm_wday     =0;
+       tm->tm_hour     =0;     tm->tm_yday     =0;
+       tm->tm_mday     =1;     tm->tm_isdst    =0;
+       tm->tm_mon      =1;
+       
+       result = palloc(sizeof(DateTime));
+       
+       len     = VARSIZE(fmt) - VARHDRSZ; 
+       
+       if (len) { 
+               tree    = (FormatNode *) palloc((len+1) * sizeof(FormatNode));  
+               (tree + len)->type = NODE_LAST;   
+       
+               pars_str = VARDATA(fmt);
+               pars_str[ len ] = '\0';
+               parse_format( tree, pars_str, keywords, suff, KWindex);
+               VARDATA(date_str)[ VARSIZE(date_str) - VARHDRSZ ] = '\0';
+               node_action(tree, VARDATA(date_str), FROM_CHAR);        
+       }
+
+#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(NOTICE, "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 (tm2datetime(tm, fsec, &tz, result) != 0)
+               elog(ERROR, "%s: can't convert 'tm' to datetime.", FUNC_NAME);
+       
+       return result;
+}
+
+/********************************************************************* 
+ *
+ * to_date
+ *
+ * Syntax:
+ *
+ *     DateADT *to_date(text *date_str, text *fmt)
+ *
+ * Purpose:
+ *
+ *     Make Date from date_str which is formated at argument 'fmt'      
+ *
+ *********************************************************************/
+
+#undef FUNC_NAME
+#define FUNC_NAME      "to_date"
+       
+DateADT  
+to_date(text *date_str, text *fmt)
+{
+       return datetime_date( from_char(date_str, fmt) );
+}
+
+
+/********************************************************************
+ *
+ * ordinal
+ *
+ * Syntax:
+ *     
+ *     text *ordinal(int4 num, text type)
+ *
+ * Purpose:
+ *
+ *     Add to number 'th' suffix and return this as text.
+ *
+ ********************************************************************/
+#undef FUNC_NAME       
+#define FUNC_NAME      "ordinal"
+text
+*ordinal(int4 num, text *typ)
+{
+       text    *result;
+       int     ttt=0;
+       
+       if (!PointerIsValid(typ))
+               return NULL;
+       
+       VARDATA(typ)[ VARSIZE(typ) - VARHDRSZ ] = '\0';
+       
+       if (!strcmp("TH", VARDATA(typ)))
+               ttt = TH_UPPER; 
+       else if (!strcmp("th", VARDATA(typ)))
+               ttt = TH_LOWER;
+       else
+               elog(ERROR, "%s: bad type '%s' (allowed: 'TH' or 'th')", 
+                        FUNC_NAME, VARDATA(typ));
+       result = (text *) palloc(16);   /* ! not int8 ! */
+       sprintf(VARDATA(result), "%d", (int) num);
+       str_numth(VARDATA(result), VARDATA(result), ttt);
+
+       VARSIZE(result) = strlen(VARDATA(result)) + VARHDRSZ;
+
+       return result;
+}
diff --git a/contrib/dateformat/to-from_char.doc b/contrib/dateformat/to-from_char.doc
new file mode 100644 (file)
index 0000000..564de88
--- /dev/null
@@ -0,0 +1,183 @@
+
+
+ TO_CHAR(datetime, text)       
+ -----------------------       
+         (returns text)           
+
+       TO_CHAR - the DateTime function for formating date and time outputs. 
+        This routine is inspire with the Oracle to_char().  
+       
+       SELECT TO_CHAR('now'::datetime, 'HH:MI:SS YYYY');
+       -------------
+       11:57:11 1999
+
+
+ FROM_CHAR(text, text)                 
+ ---------------------
+    (returns DateTime)
+
+       FROM_CHAR - the PostgreSQL extension routine which read non-datetime
+        string and convert it to DateTime. This func. is inspire with the 
+       Oracle to_date() routine, but in Oracle this func. return date only
+       and not support all keywords (format pictures).
+
+       SELECT FROM_CHAR('11:57:11 1999', 'HH:MI:SS YYYY');
+       ----------------------------
+       Fri 01 Jan 11:57:11 1999 CET
+
+
+ TO_DATE(text, text)           
+ -------------------
+      (returns Date)
+       TO_DATE - the Date function which read non-datetime (non-date) string
+       and convert it to date. All for thos func. is just as from_char().
+       This func. is inspire with the Oracle to_date() routine.
+
+       SELECT TO_DATE('11:57:11 1999', 'HH:MI:SS YYYY');
+       ----------
+       01-01-1999
+
+
+
+ ----------------------------------
+ String format-KeyWords and options: 
+ ----------------------------------
+
+       * TO_CHAR   (..., 'format picture')
+       * FROM_CHAR (..., 'format picture')     
+       * TO_DATE   (..., 'format picture')
+
+       (Note: In Oracle manual is format-keyword called 'format pictures'.)     
+
+       All keywords has suffixes (prefix or postfix), example for 2 hours:     
+               keyword: HH     (hour)            'HH'     --> '02'
+               prefix:  FM     (fill mode)       'FMHH'   --> '2'
+               postfix: TH     (ordinal number)  'HHth'   --> '02nd'
+                                                 'FMHHth' --> '2nd'    
+
+       Suffixes:
+        --------
+               FM - fill mode
+                       02        --> FMHH      --> 2           
+                       January , --> FMMonth   --> January,
+                       
+               TH - upper ordinal number
+                       02        --> HHTH      --> 02ND
+               
+               th - lower ordinal number
+                       02        --> HHth      --> 02th                        
+
+
+       KeyWords (format pictures):
+       --------------------------      
+
+               HH      - hour of day (01-12)
+               HH12    -  -- // --
+               HH24    - hour (00-24)
+               MI      - minute (00-59)
+               SS      - socond (00-59)
+               SSSS    - seconds past midnight (0-86399)       
+               Y,YYY   - year with comma (full PgSQL datetime range) digits) 
+               YYYY    - year (4 and more (full PgSQL datetime range) digits) 
+               YYY     - last 3 digits of year 
+               YY      - last 2 digits of year 
+               Y       - last digit of year 
+               MONTH   - full month name (upper) (9-letters)
+               Month   - full month name - first character is upper (9-letters)
+               month   - full month name - all characters is upper (9-letters) 
+               MON     - abbreviated month name (3-letters)
+               Mon     - abbreviated month name (3-letters) - first character is upper 
+               mon     - abbreviated month name (3-letters) - all characters is upper 
+               MM      - month (01-12)
+               DAY     - full day name (upper) (9-letters)
+               Day     - full day name - first character is upper (9-letters)
+               day     - full day name - all characters is upper (9-letters)
+               DY      - abbreviated day name (3-letters) (upper)
+               Dy      - abbreviated day name (3-letters) - first character is upper 
+               Dy      - abbreviated day name (3-letters) - all character is upper 
+               DDD     - day of year (001-366)
+               DD      - day of month (01-31)
+               D       - day of week (1-7; SUN=1)
+               WW      - week number of year
+               CC      - century (2-digits)
+               Q       - quarter
+               RM      - roman numeral month (I=JAN; I-XII)
+               W       - week of month 
+               J       - julian day (days since January 1, 4712 BC)
+
+
+       AC / BC:
+       -------
+
+               TO-FROM CHAR routines support BC and AC postfix for years.
+               You can combine BC and AC with TH.
+
+       OTHER:
+        -----
+               '\'     - must be use as double \\
+
+                       '\\HH\\MI\\SS'   -->    11\45\56 
+
+               '"'     - string berween a quotation marks is skipen and not
+                       is parsed. If you wand write '"' to output you must
+                       use \\"
+       
+                       '"Month: "Month'   -->  Month: November 
+                       '\\"YYYY Month\\"' -->  "1999 November "
+
+               text    - the PostgreSQL TO-FROM CHAR support text without '"',
+                       but " text " is fastly and you have guarantee,
+                       that this text not will interprete as keyword.  
+
+       WARNING:
+       -------
+
+               You DON'T OMIT differention between fill mode (FM prefix)
+               and standard input in FROM_CHAR (TO_DATE), because this
+               routines can't scan your input string and conver it to
+               Datetime. See:  
+
+               WRONG:          FROM_CHAR('August 1999', 'Month YYYY');
+
+               RIGHT:          FROM_CHAR('August    1999', 'Month YYYY');
+                       or      FROM_CHAR('August 1999', 'FMMonth YYYY');       
+
+               (! Month is 9-letters string if you not set fill-mode !)  
+
+
+---------------------------
+TODO / Now is not supported:
+---------------------------
+
+       - spelled-out SP suffix ( 22 --> Twenty-two )
+       - AM/PM
+
+       - not supported number to character converting
+       
+               TO_CHAR(number, 'format')
+               
+
+
+-------------------------------------------------------------------------------
+- secondary products :-) ------------------------------------------------------
+-------------------------------------------------------------------------------
+
+
+ORDINAL(int4, text)
+-------------------    
+
+       * Translate number to ordinal number and return this as text
+
+
+* Examples: 
+
+template1=> select ordinal(21212, 'TH');
+ordinal
+-------
+21212ND
+
+template1=> select ordinal(21212, 'th');
+ordinal
+-------
+21212nd
diff --git a/contrib/dateformat/to-from_char.h b/contrib/dateformat/to-from_char.h
new file mode 100644 (file)
index 0000000..e96e0a3
--- /dev/null
@@ -0,0 +1,18 @@
+
+#ifndef TO_FROM_CHAR_H
+#define TO_FROM_CHAR_H
+
+/*------ 
+ * For postgres 
+ *------ 
+ */
+extern text    *to_char(DateTime *dt, text *format);
+extern DateTime *from_char(text *date_str, text *format);
+extern DateADT to_date(text *date_str, text *format);
+
+extern text    *ordinal(int4 num, text *type);
+
+extern char    *months_full[];         /* full months name             */
+extern char    *rm_months[];           /* roman numeral of months      */
+
+#endif
\ No newline at end of file
diff --git a/contrib/dateformat/to-from_char.sql.in b/contrib/dateformat/to-from_char.sql.in
new file mode 100644 (file)
index 0000000..102a24f
--- /dev/null
@@ -0,0 +1,29 @@
+-- to-from_char.sql datetime routines --
+--
+-- Copyright (c) 1999, Karel Zak "Zakkr" <zakkr@zf.jcu.cz>
+--
+-- This file is distributed under the GNU General Public License
+-- either version 2, or (at your option) any later version.
+
+
+-- Define the new functions
+--
+
+create function to_char(datetime, text) returns text
+  as 'MODULE_PATHNAME' 
+  language 'c';
+
+create function from_char(text, text) returns datetime
+  as 'MODULE_PATHNAME' 
+  language 'c';
+
+create function to_date(text, text) returns date
+  as 'MODULE_PATHNAME' 
+  language 'c';
+create function ordinal(int, text) returns text
+  as 'MODULE_PATHNAME' 
+  language 'c';
+
+
+-- end of file
\ No newline at end of file