I finish devel. of Oracle compatible DateTime routines TO_CHAR(),
authorBruce Momjian
Mon, 29 Nov 1999 23:26:18 +0000 (23:26 +0000)
committerBruce Momjian
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 
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 
 
+bit -
+   Bit type
+   by Adriaan Joubert 
+
+dateformat -
+   Date Formatting to/from character strings
+   by Karel Zak - Zakkr 
+
 datetime -
    Date & time functions
    by Massimo Dal Zotto 
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    
+   
+   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 
+#include 
+#include 
+#include 
+
+
+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    \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" 
+ *
+ *   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 
+#include 
+#include 
+#include 
+#include 
+
+#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" 
+--
+-- 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