diff --git a/contrib/README b/contrib/README index a59880c265..e280186cb3 100644 --- a/contrib/README +++ b/contrib/README @@ -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 index 0000000000..849d7a777e --- /dev/null +++ b/contrib/dateformat/Makefile @@ -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 index 0000000000..6eb539c3aa --- /dev/null +++ b/contrib/dateformat/test/Makefile @@ -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 index 0000000000..63177f2ee5 --- /dev/null +++ b/contrib/dateformat/test/README @@ -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 index 0000000000..6a96776b9b --- /dev/null +++ b/contrib/dateformat/test/rand_datetime.c @@ -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 index 0000000000..f3c2815fab --- /dev/null +++ b/contrib/dateformat/test/regress.sql @@ -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 index 0000000000..eebd7a8c64 --- /dev/null +++ b/contrib/dateformat/to-from_char.c @@ -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 index 0000000000..564de88189 --- /dev/null +++ b/contrib/dateformat/to-from_char.doc @@ -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 index 0000000000..e96e0a3797 --- /dev/null +++ b/contrib/dateformat/to-from_char.h @@ -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 index 0000000000..102a24ff46 --- /dev/null +++ b/contrib/dateformat/to-from_char.sql.in @@ -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