Started adding date and timestamp.
authorMichael Meskes
Thu, 20 Mar 2003 15:56:50 +0000 (15:56 +0000)
committerMichael Meskes
Thu, 20 Mar 2003 15:56:50 +0000 (15:56 +0000)
22 files changed:
src/interfaces/ecpg/ChangeLog
src/interfaces/ecpg/ecpglib/data.c
src/interfaces/ecpg/ecpglib/execute.c
src/interfaces/ecpg/ecpglib/typename.c
src/interfaces/ecpg/include/decimal.h
src/interfaces/ecpg/include/ecpgtype.h
src/interfaces/ecpg/include/pgtypes_date.h [new file with mode: 0644]
src/interfaces/ecpg/include/pgtypes_error.h
src/interfaces/ecpg/include/pgtypes_timestamp.h [new file with mode: 0644]
src/interfaces/ecpg/pgtypeslib/Makefile
src/interfaces/ecpg/pgtypeslib/common.c [new file with mode: 0644]
src/interfaces/ecpg/pgtypeslib/datetime.c [new file with mode: 0644]
src/interfaces/ecpg/pgtypeslib/dt.h [new file with mode: 0644]
src/interfaces/ecpg/pgtypeslib/dt_common.c [new file with mode: 0644]
src/interfaces/ecpg/pgtypeslib/extern.h [new file with mode: 0644]
src/interfaces/ecpg/pgtypeslib/numeric.c
src/interfaces/ecpg/pgtypeslib/timestamp.c [new file with mode: 0644]
src/interfaces/ecpg/preproc/preproc.y
src/interfaces/ecpg/preproc/type.c
src/interfaces/ecpg/test/Makefile
src/interfaces/ecpg/test/dt_test.pgc [new file with mode: 0644]
src/interfaces/ecpg/test/num_test.pgc

index e4c3548dfc44e69ff17e4ea6cc62ff3bdce95155..bfe62f1fda5667ceed837454788672f88d652e7b 100644 (file)
@@ -1360,6 +1360,10 @@ Sun Mar 16 11:28:01 CET 2003
    - Started with a pgtypes library.
    - Renamed lib directory to ecpglib.
    - Added numerical functions to library and preprocessor.
+
+Don Mar 20 16:53:40 CET 2003
+
+   - Added date/timestamp to library and preprocessor.
    - Set ecpg version to 2.12.0.
    - Set ecpg library to 3.4.2.
    - Set pgtypes library to 1.0.0
index 85d5e30a1ba9e8afc2bb23ec01060ef3fb2fafbc..81f9d62e691964f94aa8ead5b43735e6a931292d 100644 (file)
@@ -1,4 +1,4 @@
-/* $Header: /cvsroot/pgsql/src/interfaces/ecpg/ecpglib/data.c,v 1.1 2003/03/16 10:42:53 meskes Exp $ */
+/* $Header: /cvsroot/pgsql/src/interfaces/ecpg/ecpglib/data.c,v 1.2 2003/03/20 15:56:50 meskes Exp $ */
 
 #include "postgres_fe.h"
 
@@ -11,6 +11,8 @@
 #include "extern.h"
 #include "sqlca.h"
 #include "pgtypes_numeric.h"
+#include "pgtypes_date.h"
+#include "pgtypes_timestamp.h"
 
 bool
 ECPGget_data(const PGresult *results, int act_tuple, int act_field, int lineno,
@@ -99,6 +101,8 @@ ECPGget_data(const PGresult *results, int act_tuple, int act_field, int lineno,
                double      dres;
                char       *scan_length;
                NumericVar *nres;
+               Date       ddres;
+               Timestamp   tres;
 
            case ECPGt_short:
            case ECPGt_int:
@@ -397,7 +401,51 @@ ECPGget_data(const PGresult *results, int act_tuple, int act_field, int lineno,
 
                PGTYPESnumeric_copy(nres, (NumericVar *)(var + offset * act_tuple));
                break;
+               
+           case ECPGt_date:
+               if (pval)
+               {
+                   if (isarray && *pval == '"')
+                       ddres = PGTYPESdate_atod(pval + 1, &scan_length);
+                   else
+                       ddres = PGTYPESdate_atod(pval, &scan_length);
+
+                   if (isarray && *scan_length == '"')
+                       scan_length++;
+
+                   if ((isarray && *scan_length != ',' && *scan_length != '}')
+                       || (!isarray && *scan_length != '\0'))  /* Garbage left */
+                   {
+                       ECPGraise(lineno, ECPG_FLOAT_FORMAT, pval);
+                       return (false);
+                   }
+
+                   *((Date *)(var + offset * act_tuple)) = ddres;
+               }
+               break;
 
+           case ECPGt_timestamp:
+               if (pval)
+               {
+                   if (isarray && *pval == '"')
+                       tres = PGTYPEStimestamp_atot(pval + 1, &scan_length);
+                   else
+                       tres = PGTYPEStimestamp_atot(pval, &scan_length);
+
+                   if (isarray && *scan_length == '"')
+                       scan_length++;
+
+                   if ((isarray && *scan_length != ',' && *scan_length != '}')
+                       || (!isarray && *scan_length != '\0'))  /* Garbage left */
+                   {
+                       ECPGraise(lineno, ECPG_FLOAT_FORMAT, pval);
+                       return (false);
+                   }
+
+                   *((Timestamp *)(var + offset * act_tuple)) = tres;
+               }
+               break;
+               
            default:
                ECPGraise(lineno, ECPG_UNSUPPORTED, ECPGtype_name(type));
                return (false);
index 8cb3fa36a6717a5b7e33f979a46cbab8c21b4d64..0fdd925a09c30140d0aec5a64ddcb4beb5713965 100644 (file)
@@ -1,4 +1,4 @@
-/* $Header: /cvsroot/pgsql/src/interfaces/ecpg/ecpglib/execute.c,v 1.3 2003/03/19 16:05:41 petere Exp $ */
+/* $Header: /cvsroot/pgsql/src/interfaces/ecpg/ecpglib/execute.c,v 1.4 2003/03/20 15:56:50 meskes Exp $ */
 
 /*
  * The aim is to get a simpler inteface to the database routines.
@@ -27,6 +27,8 @@
 #include "sqlca.h"
 #include "sql3types.h"
 #include "pgtypes_numeric.h"
+#include "pgtypes_date.h"
+#include "pgtypes_timestamp.h"
 
 /* variables visible to the programs */
 struct sqlca sqlca =
@@ -59,8 +61,7 @@ struct sqlca sqlca =
 /* This function returns a newly malloced string that has the  \
    in the argument quoted with \ and the ' quoted with ' as SQL92 says.
  */
-static
-char *
+static char *
 quote_postgres(char *arg, int lineno)
 {
    char       *res = (char *) ECPGalloc(2 * strlen(arg) + 3, lineno);
@@ -876,6 +877,89 @@ ECPGstore_input(const struct statement * stmt, const struct variable * var,
                    free(str);
                }
                break;
+
+           case ECPGt_date:
+               {
+                   char *str = NULL;
+                   int slen;
+                   
+                   if (var->arrsize > 1)
+                   {
+                       for (element = 0; element < var->arrsize; element++)
+                       {
+                           str = PGTYPESdate_dtoa(*(Date *)((var + var->offset * element)->value));
+                           slen = strlen (str);
+                           
+                           if (!(mallocedval = ECPGrealloc(mallocedval, strlen(mallocedval) + slen + 5, stmt->lineno)))
+                               return false;
+                           
+                           if (!element)
+                               strcpy(mallocedval, "'{");
+                           
+                           strncpy(mallocedval + strlen(mallocedval), str , slen + 1);
+                           strcpy(mallocedval + strlen(mallocedval), ",");
+                       }
+                       strcpy(mallocedval + strlen(mallocedval) - 1, "}'");
+                   }
+                   else
+                   {
+                       str = PGTYPESdate_dtoa(*(Date *)(var->value));
+                       slen = strlen (str);
+                   
+                       if (!(mallocedval = ECPGalloc(slen + 1, stmt->lineno)))
+                           return false;
+
+                       strncpy(mallocedval, str , slen);
+                       mallocedval[slen] = '\0';
+                   }
+                   
+                   *tobeinserted_p = mallocedval;
+                   *malloced_p = true;
+                   free(str);
+               }
+               break;
+               
+           case ECPGt_timestamp:
+               {
+                   char *str = NULL;
+                   int slen;
+                   
+                   if (var->arrsize > 1)
+                   {
+                       for (element = 0; element < var->arrsize; element++)
+                       {
+                           str = PGTYPEStimestamp_ttoa(*(Timestamp *)((var + var->offset * element)->value));
+                           slen = strlen (str);
+                           
+                           if (!(mallocedval = ECPGrealloc(mallocedval, strlen(mallocedval) + slen + 5, stmt->lineno)))
+                               return false;
+                           
+                           if (!element)
+                               strcpy(mallocedval, "'{");
+                           
+                           strncpy(mallocedval + strlen(mallocedval), str , slen + 1);
+                           strcpy(mallocedval + strlen(mallocedval), ",");
+                       }
+                       strcpy(mallocedval + strlen(mallocedval) - 1, "}'");
+                   }
+                   else
+                   {
+                       str = PGTYPEStimestamp_ttoa(*(Timestamp *)(var->value));
+                       slen = strlen (str);
+                   
+                       if (!(mallocedval = ECPGalloc(slen + 1, stmt->lineno)))
+                           return false;
+
+                       strncpy(mallocedval, str , slen);
+                       mallocedval[slen] = '\0';
+                   }
+                   
+                   *tobeinserted_p = mallocedval;
+                   *malloced_p = true;
+                   free(str);
+               }
+               break;
+               
            default:
                /* Not implemented yet */
                ECPGraise(stmt->lineno, ECPG_UNSUPPORTED, (char *) ECPGtype_name(var->type));
index c970f767242dc7a2fff5f33ec06f8d362b437178..6ae846c38128e1d8ddc97dd20e051e3e218dcd1d 100644 (file)
@@ -1,4 +1,4 @@
-/* $Header: /cvsroot/pgsql/src/interfaces/ecpg/ecpglib/typename.c,v 1.1 2003/03/16 10:42:53 meskes Exp $ */
+/* $Header: /cvsroot/pgsql/src/interfaces/ecpg/ecpglib/typename.c,v 1.2 2003/03/20 15:56:50 meskes Exp $ */
 
 #include "postgres_fe.h"
 
@@ -49,6 +49,10 @@ ECPGtype_name(enum ECPGttype typ)
            return "char";
        case ECPGt_numeric:
            return "numeric";
+       case ECPGt_date:
+           return "date";
+       case ECPGt_timestamp:
+           return "timestamp";
        default:
            abort();
    }
index f307207adce9fcd7d6b1e360a503bd653018a7f5..2c13a33c70860dd0b3b544abb7ccfcee0876636c 100644 (file)
@@ -2,4 +2,14 @@
 
 #ifndef dec_t
 #define dec_t NumericVar
+
+#define CSHORTTYPE 0
+#define CMONEYTYPE 0
+#define CCHARTYPE 0
+#define CDECIMALTYPE 0
+#define CINTTYPE 0
+#define CDATETYPE 0
+#define CDOUBLETYPE 0
+#define CLONGTYPE 0
+
 #endif /* dec_t */
index 6ed5f5d3e4f7741f5932d24baf79d57cdac731d1..31738d421f1406f5a1deeccf08a6b3c646f17d84 100644 (file)
@@ -52,7 +52,9 @@ enum ECPGttype
    ECPGt_NO_INDICATOR,         /* no indicator */
    ECPGt_long_long, ECPGt_unsigned_long_long,
    ECPGt_descriptor,           /* sql descriptor, no C variable */
-   ECPGt_numeric
+   ECPGt_numeric,
+   ECPGt_date,
+   ECPGt_timestamp
 };
 
  /* descriptor items */
diff --git a/src/interfaces/ecpg/include/pgtypes_date.h b/src/interfaces/ecpg/include/pgtypes_date.h
new file mode 100644 (file)
index 0000000..882ddab
--- /dev/null
@@ -0,0 +1,12 @@
+#ifndef PGTYPES_DATETIME
+#define PGTYPES_DATETIME
+
+#define Date long
+
+extern Date PGTYPESdate_atod(char *, char **);
+extern char *PGTYPESdate_dtoa(Date);
+extern int PGTYPESdate_julmdy(Date, int*);
+extern int PGTYPESdate_mdyjul(int*, Date *);
+extern int PGTYPESdate_day(Date);
+
+#endif /* PGTYPES_DATETIME */
index e997b03ae014b64cd5785a199d96e7d592e90c9f..7cab1971b98960fd7dc4ffddabd16f2cf20ec462 100644 (file)
@@ -2,5 +2,7 @@
 #define PGTYPES_BAD_NUMERIC    202
 #define PGTYPES_DIVIDE_ZERO    203
 
-#define PGTYPES_BAD_DATE   300
+#define PGTYPES_BAD_DATE   210
+
+#define PGTYPES_BAD_TIMESTAMP  220
 
diff --git a/src/interfaces/ecpg/include/pgtypes_timestamp.h b/src/interfaces/ecpg/include/pgtypes_timestamp.h
new file mode 100644 (file)
index 0000000..48a54b1
--- /dev/null
@@ -0,0 +1,16 @@
+#ifndef PGTYPES_TIMESTAMP
+#define PGTYPES_TIMESTAMP
+
+#ifdef HAVE_INT64_TIMESTAMP
+typedef int64 Timestamp;
+typedef int64 TimestampTz;
+
+#else
+typedef double Timestamp;
+typedef double TimestampTz;
+#endif
+
+extern Timestamp PGTYPEStimestamp_atot(char *, char **);
+extern char *PGTYPEStimestamp_ttoa(Timestamp);
+
+#endif /* PGTYPES_TIMESTAMP */
index 73d174e3d23bb477f4733dbf42d25d5dfe41e1af..ae691c7d21d632379e96249b14881c91f6e6e0c9 100644 (file)
@@ -4,7 +4,7 @@
 #
 # Copyright (c) 1994, Regents of the University of California
 #
-# $Header: /cvsroot/pgsql/src/interfaces/ecpg/pgtypeslib/Makefile,v 1.1 2003/03/16 10:42:54 meskes Exp $
+# $Header: /cvsroot/pgsql/src/interfaces/ecpg/pgtypeslib/Makefile,v 1.2 2003/03/20 15:56:50 meskes Exp $
 #
 #-------------------------------------------------------------------------
 
@@ -18,7 +18,7 @@ SO_MINOR_VERSION= 0.0
 
 override CPPFLAGS := -g -I$(top_srcdir)/src/interfaces/ecpg/include -I$(top_srcdir)/src/include/utils $(CPPFLAGS)
 
-OBJS= numeric.o
+OBJS= numeric.o datetime.o common.o dt_common.o timestamp.o
 
 all: all-lib
 
diff --git a/src/interfaces/ecpg/pgtypeslib/common.c b/src/interfaces/ecpg/pgtypeslib/common.c
new file mode 100644 (file)
index 0000000..b91bedc
--- /dev/null
@@ -0,0 +1,29 @@
+#include 
+
+#include "extern.h"
+   
+char *
+pgtypes_alloc(long size)
+{
+   char *new = (char *) calloc(1L, size);
+
+   if (!new)
+   {
+       errno = ENOMEM;
+       return NULL;
+   }
+
+   memset(new, '\0', size);
+   return (new);
+}
+
+char *
+pgtypes_strdup(char *str)
+{
+   char *new = (char *) strdup(str);
+
+   if (!new)
+       errno = ENOMEM;
+   return (new);
+}
+
diff --git a/src/interfaces/ecpg/pgtypeslib/datetime.c b/src/interfaces/ecpg/pgtypeslib/datetime.c
new file mode 100644 (file)
index 0000000..46fd1ab
--- /dev/null
@@ -0,0 +1,105 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include "dt.h"
+#include "extern.h"
+#include "pgtypes_error.h"
+#include "pgtypes_date.h"
+
+Date
+PGTYPESdate_atod(char *str, char **endptr)
+{
+   
+   Date        dDate;
+   fsec_t      fsec;
+   struct tm   tt,
+              *tm = &tt;
+   int         tzp;
+   int         dtype;
+   int         nf;
+   char       *field[MAXDATEFIELDS];
+   int         ftype[MAXDATEFIELDS];
+   char        lowstr[MAXDATELEN + 1];
+   char        *realptr;
+   char **ptr = (endptr != NULL) ? endptr : &realptr;
+   
+   bool        EuroDates = FALSE;
+
+   if (strlen(str) >= sizeof(lowstr))
+   {
+       errno = PGTYPES_BAD_DATE;
+       return -1;
+   }
+
+   if ((ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf, ptr) != 0)
+    || (DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tzp, EuroDates) != 0))
+   {
+       errno = PGTYPES_BAD_DATE;
+       return -1;
+   }
+
+   switch (dtype)
+   {
+       case DTK_DATE:
+           break;
+
+       case DTK_EPOCH:
+           GetEpochTime(tm); 
+           break;
+
+       default:
+           errno = PGTYPES_BAD_DATE;
+           return -1;
+   }
+
+   dDate = (date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - date2j(2000, 1, 1));
+
+   return dDate;
+}
+
+char *
+PGTYPESdate_dtoa(Date dDate)
+{
+   struct tm       tt, *tm = &tt;
+   char            buf[MAXDATELEN + 1];
+   int DateStyle=0;
+   bool        EuroDates = FALSE;
+                          
+   j2date((dDate + date2j(2000, 1, 1)), &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday));
+   EncodeDateOnly(tm, DateStyle, buf, EuroDates);
+   return pgtypes_strdup(buf);
+}
+
+int
+PGTYPESdate_julmdy(Date jd, int* mdy)
+{
+   printf("day: %d\n", mdy[0]);
+   printf("month: %d\n", mdy[1]);
+   printf("year: %d\n", mdy[2]);
+   j2date((int) jd, mdy+2, mdy+1, mdy+0);
+   return 0;
+}
+
+int
+PGTYPESdate_mdyjul(int* mdy, Date *jdate)
+{
+   /* month is mdy[0] */
+   /* day   is mdy[1] */
+   /* year  is mdy[2] */
+   printf("day: %d\n", mdy[1]);
+   printf("month: %d\n", mdy[0]);
+   printf("year: %d\n", mdy[2]);
+   *jdate = (Date) date2j(mdy[2], mdy[0], mdy[1]);
+   return 0;
+}
+
+int
+PGTYPESdate_day(Date dDate)
+{
+   return j2day(dDate);
+}
+
diff --git a/src/interfaces/ecpg/pgtypeslib/dt.h b/src/interfaces/ecpg/pgtypeslib/dt.h
new file mode 100644 (file)
index 0000000..d20c0cd
--- /dev/null
@@ -0,0 +1,303 @@
+#ifndef DT_H
+#define DT_H
+
+#define MAXTZLEN             10
+
+#ifdef HAVE_INT64_TIMESTAMP
+
+typedef int32 fsec_t;
+
+#else
+
+typedef double fsec_t;
+
+#define TIME_PREC_INV 1000000.0
+#define JROUND(j) (rint(((double) (j))*TIME_PREC_INV)/TIME_PREC_INV)
+#endif
+
+#ifndef bool
+#define bool char
+#endif   /* ndef bool */
+
+#ifndef FALSE
+#define FALSE   0
+#endif   /* FALSE */
+
+#ifndef TRUE
+#define TRUE       1
+#endif  /* TRUE */
+
+#define USE_POSTGRES_DATES              0
+#define USE_ISO_DATES                   1
+#define USE_SQL_DATES                   2
+#define USE_GERMAN_DATES                3
+
+#define DAGO           "ago"
+#define EPOCH          "epoch"
+#define INVALID            "invalid"
+#define EARLY          "-infinity"
+#define LATE           "infinity"
+#define NOW                "now"
+#define TODAY          "today"
+#define TOMORROW       "tomorrow"
+#define YESTERDAY      "yesterday"
+#define ZULU           "zulu"
+
+#define DMICROSEC      "usecond"
+#define DMILLISEC      "msecond"
+#define DSECOND            "second"
+#define DMINUTE            "minute"
+#define DHOUR          "hour"
+#define DDAY           "day"
+#define DWEEK          "week"
+#define DMONTH         "month"
+#define DQUARTER       "quarter"
+#define DYEAR          "year"
+#define DDECADE            "decade"
+#define DCENTURY       "century"
+#define DMILLENNIUM        "millennium"
+#define DA_D           "ad"
+#define DB_C           "bc"
+#define DTIMEZONE      "timezone"
+#define DCURRENT           "current"
+
+/*
+ * Fundamental time field definitions for parsing.
+ *
+ * Meridian:  am, pm, or 24-hour style.
+ * Millennium: ad, bc
+ */
+
+#define AM     0
+#define PM     1
+#define HR24   2
+
+#define AD     0
+#define BC     1
+
+/*
+ * Fields for time decoding.
+ *
+ * Can't have more of these than there are bits in an unsigned int
+ * since these are turned into bit masks during parsing and decoding.
+ *
+ * Furthermore, the values for YEAR, MONTH, DAY, HOUR, MINUTE, SECOND
+ * must be in the range 0..14 so that the associated bitmasks can fit
+ * into the left half of an INTERVAL's typmod value.
+ */
+
+#define RESERV 0
+#define MONTH  1
+#define YEAR   2
+#define DAY        3
+#define JULIAN 4
+#define TZ     5
+#define DTZ        6
+#define DTZMOD 7
+#define IGNORE_DTF 8
+#define AMPM   9
+#define HOUR   10
+#define MINUTE 11
+#define SECOND 12
+#define DOY        13
+#define DOW        14
+#define UNITS  15
+#define ADBC   16
+/* these are only for relative dates */
+#define AGO        17
+#define ABS_BEFORE     18
+#define ABS_AFTER      19
+/* generic fields to help with parsing */
+#define ISODATE 20
+#define ISOTIME 21
+/* reserved for unrecognized string values */
+#define UNKNOWN_FIELD  31
+
+/*
+ * Token field definitions for time parsing and decoding.
+ * These need to fit into the datetkn table type.
+ * At the moment, that means keep them within [-127,127].
+ * These are also used for bit masks in DecodeDateDelta()
+ * so actually restrict them to within [0,31] for now.
+ * - thomas 97/06/19
+ * Not all of these fields are used for masks in DecodeDateDelta
+ * so allow some larger than 31. - thomas 1997-11-17
+ */
+
+#define DTK_NUMBER     0
+#define DTK_STRING     1
+
+#define DTK_DATE       2
+#define DTK_TIME       3
+#define DTK_TZ         4
+#define DTK_AGO            5
+
+#define DTK_SPECIAL        6
+#define DTK_INVALID        7
+#define DTK_CURRENT        8
+#define DTK_EARLY      9
+#define DTK_LATE       10
+#define DTK_EPOCH      11
+#define DTK_NOW            12
+#define DTK_YESTERDAY  13
+#define DTK_TODAY      14
+#define DTK_TOMORROW   15
+#define DTK_ZULU       16
+
+#define DTK_DELTA      17
+#define DTK_SECOND     18
+#define DTK_MINUTE     19
+#define DTK_HOUR       20
+#define DTK_DAY            21
+#define DTK_WEEK       22
+#define DTK_MONTH      23
+#define DTK_QUARTER        24
+#define DTK_YEAR       25
+#define DTK_DECADE     26
+#define DTK_CENTURY        27
+#define DTK_MILLENNIUM 28
+#define DTK_MILLISEC   29
+#define DTK_MICROSEC   30
+#define DTK_JULIAN     31
+
+#define DTK_DOW            32
+#define DTK_DOY            33
+#define DTK_TZ_HOUR        34
+#define DTK_TZ_MINUTE  35
+
+
+/*
+ * Bit mask definitions for time parsing.
+ */
+
+#define DTK_M(t)       (0x01 << (t))
+
+#define DTK_DATE_M     (DTK_M(YEAR) | DTK_M(MONTH) | DTK_M(DAY))
+#define DTK_TIME_M     (DTK_M(HOUR) | DTK_M(MINUTE) | DTK_M(SECOND))
+
+#define MAXDATELEN     51      /* maximum possible length of an input
+                                * date string (not counting tr. null) */
+#define MAXDATEFIELDS  25      /* maximum possible number of fields in a
+                                * date string */
+#define TOKMAXLEN      10      /* only this many chars are stored in
+                                * datetktbl */
+
+/* keep this struct small; it gets used a lot */
+typedef struct
+{
+#if defined(_AIX)
+   char       *token;
+#else
+   char        token[TOKMAXLEN];
+#endif   /* _AIX */
+   char        type;
+   char        value;          /* this may be unsigned, alas */
+} datetkn;
+
+
+/* TMODULO()
+ * Macro to replace modf(), which is broken on some platforms.
+ * t = input and remainder
+ * q = integer part
+ * u = divisor
+ */
+#ifdef HAVE_INT64_TIMESTAMP
+#define TMODULO(t,q,u) \
+do { \
+   q = (t / u); \
+   if (q != 0) t -= (q * u); \
+} while(0)
+#else
+#define TMODULO(t,q,u) \
+do { \
+   q = ((t < 0)? ceil(t / u): floor(t / u)); \
+   if (q != 0) t -= rint(q * u); \
+} while(0)
+#endif
+
+/* Global variable holding time zone information. */
+#if defined(__CYGWIN__) || defined(N_PLAT_NLM)
+#define TIMEZONE_GLOBAL _timezone
+#else
+#define TIMEZONE_GLOBAL timezone
+#endif
+
+/*
+ * Date/time validation
+ * Include check for leap year.
+ */
+
+extern int day_tab[2][13];
+
+#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
+
+/* Julian date support for date2j() and j2date()
+ * Set the minimum year to one greater than the year of the first valid day
+ * to avoid having to check year and day both. - tgl 97/05/08
+ */
+
+#define JULIAN_MINYEAR (-4713)
+#define JULIAN_MINMONTH (11)
+#define JULIAN_MINDAY (24)
+
+#define IS_VALID_JULIAN(y,m,d) (((y) > JULIAN_MINYEAR) \
+ || (((y) == JULIAN_MINYEAR) && (((m) > JULIAN_MINMONTH) \
+  || (((m) == JULIAN_MINMONTH) && ((d) >= JULIAN_MINDAY)))))
+
+#define UTIME_MINYEAR (1901)
+#define UTIME_MINMONTH (12)
+#define UTIME_MINDAY (14)
+#define UTIME_MAXYEAR (2038)
+#define UTIME_MAXMONTH (01)
+#define UTIME_MAXDAY (18)
+
+#define IS_VALID_UTIME(y,m,d) ((((y) > UTIME_MINYEAR) \
+ || (((y) == UTIME_MINYEAR) && (((m) > UTIME_MINMONTH) \
+  || (((m) == UTIME_MINMONTH) && ((d) >= UTIME_MINDAY))))) \
+ && (((y) < UTIME_MAXYEAR) \
+ || (((y) == UTIME_MAXYEAR) && (((m) < UTIME_MAXMONTH) \
+  || (((m) == UTIME_MAXMONTH) && ((d) <= UTIME_MAXDAY))))))
+
+#ifdef HUGE_VAL
+#define DT_NOBEGIN              (-HUGE_VAL)
+#define DT_NOEND                (HUGE_VAL)
+#else
+#define DT_NOBEGIN              (-DBL_MAX)
+#define DT_NOEND                (DBL_MAX)
+#endif
+
+#define TIMESTAMP_NOBEGIN(j)    do {j = DT_NOBEGIN;} while (0)
+#define TIMESTAMP_NOEND(j)          do {j = DT_NOEND;} while (0)
+#define TIMESTAMP_IS_NOBEGIN(j) ((j) == DT_NOBEGIN)
+#define TIMESTAMP_IS_NOEND(j)   ((j) == DT_NOEND)
+#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+
+extern int DecodeTimeOnly(char **field, int *ftype,
+              int nf, int *dtype,
+              struct tm * tm, fsec_t *fsec, int *tzp);
+
+extern int DecodeInterval(char **field, int *ftype,
+              int nf, int *dtype,
+              struct tm * tm, fsec_t *fsec);
+
+extern int EncodeTimeOnly(struct tm * tm, fsec_t fsec, int *tzp, int style, char *str);
+extern int EncodeDateTime(struct tm * tm, fsec_t fsec, int *tzp, char **tzn, int style, char *str, bool);
+extern int EncodeInterval(struct tm * tm, fsec_t fsec, int style, char *str);
+
+extern int DecodeUnits(int field, char *lowtoken, int *val);
+extern bool    ClearDateCache(bool, bool, bool);
+
+extern int j2day(int jd);
+
+extern bool CheckDateTokenTables(void);
+
+extern int EncodeDateOnly(struct tm *, int, char *, bool);
+extern void GetEpochTime(struct tm *);
+extern int ParseDateTime(char *, char *, char **, int *, int, int *, char **);
+extern int DecodeDateTime(char **, int *, int, int *, struct tm *, fsec_t *, int *, bool);
+extern void j2date(int, int *, int *, int *);
+extern int date2j(int, int, int);
+extern double rint(double x);
+
+#endif   /* DT_H */
+   
diff --git a/src/interfaces/ecpg/pgtypeslib/dt_common.c b/src/interfaces/ecpg/pgtypeslib/dt_common.c
new file mode 100644 (file)
index 0000000..3b85f7d
--- /dev/null
@@ -0,0 +1,2558 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include "dt.h"
+#include "extern.h"
+
+static int day_tab[2][13] = {
+           {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0},
+       {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0}};
+
+typedef long AbsoluteTime;
+
+#define ABS_SIGNBIT             ((char) 0200)
+#define POS(n)                  (n)
+#define NEG(n)                  ((n)|ABS_SIGNBIT)
+#define FROMVAL(tp)             (-SIGNEDCHAR((tp)->value) * 15) /* uncompress */
+#define VALMASK                 ((char) 0177)
+#define SIGNEDCHAR(c)   ((c)&ABS_SIGNBIT? -((c)&VALMASK): (c))
+
+static datetkn datetktbl[] = {
+/* text, token, lexval */
+   {EARLY, RESERV, DTK_EARLY}, /* "-infinity" reserved for "early time" */
+   {"abstime", IGNORE_DTF, 0}, /* for pre-v6.1 "Invalid Abstime" */
+   {"acsst", DTZ, POS(42)},    /* Cent. Australia */
+   {"acst", DTZ, NEG(16)},     /* Atlantic/Porto Acre */
+   {"act", TZ, NEG(20)},       /* Atlantic/Porto Acre */
+   {DA_D, ADBC, AD},           /* "ad" for years >= 0 */
+   {"adt", DTZ, NEG(12)},      /* Atlantic Daylight Time */
+   {"aesst", DTZ, POS(44)},    /* E. Australia */
+   {"aest", TZ, POS(40)},      /* Australia Eastern Std Time */
+   {"aft", TZ, POS(18)},       /* Kabul */
+   {"ahst", TZ, NEG(40)},      /* Alaska-Hawaii Std Time */
+   {"akdt", DTZ, NEG(32)},     /* Alaska Daylight Time */
+   {"akst", DTZ, NEG(36)},     /* Alaska Standard Time */
+   {"allballs", RESERV, DTK_ZULU},     /* 00:00:00 */
+   {"almst", TZ, POS(28)},     /* Almaty Savings Time */
+   {"almt", TZ, POS(24)},      /* Almaty Time */
+   {"am", AMPM, AM},
+   {"amst", DTZ, POS(20)},     /* Armenia Summer Time (Yerevan) */
+#if 0
+   {"amst", DTZ, NEG(12)},     /* Porto Velho */
+#endif
+   {"amt", TZ, POS(16)},       /* Armenia Time (Yerevan) */
+   {"anast", DTZ, POS(52)},    /* Anadyr Summer Time (Russia) */
+   {"anat", TZ, POS(48)},      /* Anadyr Time (Russia) */
+   {"apr", MONTH, 4},
+   {"april", MONTH, 4},
+#if 0
+   aqtst
+   aqtt
+   arst
+#endif
+   {"art", TZ, NEG(12)},       /* Argentina Time */
+#if 0
+   ashst
+   ast                         /* Atlantic Standard Time, Arabia Standard
+                                * Time, Acre Standard Time */
+#endif
+   {"ast", TZ, NEG(16)},       /* Atlantic Std Time (Canada) */
+   {"at", IGNORE_DTF, 0},      /* "at" (throwaway) */
+   {"aug", MONTH, 8},
+   {"august", MONTH, 8},
+   {"awsst", DTZ, POS(36)},    /* W. Australia */
+   {"awst", TZ, POS(32)},      /* W. Australia */
+   {"awt", DTZ, NEG(12)},
+   {"azost", DTZ, POS(0)},     /* Azores Summer Time */
+   {"azot", TZ, NEG(4)},       /* Azores Time */
+   {"azst", DTZ, POS(20)},     /* Azerbaijan Summer Time */
+   {"azt", TZ, POS(16)},       /* Azerbaijan Time */
+   {DB_C, ADBC, BC},           /* "bc" for years < 0 */
+   {"bdst", TZ, POS(8)},       /* British Double Summer Time */
+   {"bdt", TZ, POS(24)},       /* Dacca */
+   {"bnt", TZ, POS(32)},       /* Brunei Darussalam Time */
+   {"bort", TZ, POS(32)},      /* Borneo Time (Indonesia) */
+#if 0
+   bortst
+   bost
+#endif
+   {"bot", TZ, NEG(16)},       /* Bolivia Time */
+   {"bra", TZ, NEG(12)},       /* Brazil Time */
+#if 0
+   brst
+   brt
+#endif
+   {"bst", DTZ, POS(4)},       /* British Summer Time */
+#if 0
+   {"bst", TZ, NEG(12)},       /* Brazil Standard Time */
+   {"bst", DTZ, NEG(44)},      /* Bering Summer Time */
+#endif
+   {"bt", TZ, POS(12)},        /* Baghdad Time */
+   {"btt", TZ, POS(24)},       /* Bhutan Time */
+   {"cadt", DTZ, POS(42)},     /* Central Australian DST */
+   {"cast", TZ, POS(38)},      /* Central Australian ST */
+   {"cat", TZ, NEG(40)},       /* Central Alaska Time */
+   {"cct", TZ, POS(32)},       /* China Coast Time */
+#if 0
+   {"cct", TZ, POS(26)},       /* Indian Cocos (Island) Time */
+#endif
+   {"cdt", DTZ, NEG(20)},      /* Central Daylight Time */
+   {"cest", DTZ, POS(8)},      /* Central European Dayl.Time */
+   {"cet", TZ, POS(4)},        /* Central European Time */
+   {"cetdst", DTZ, POS(8)},    /* Central European Dayl.Time */
+   {"chadt", DTZ, POS(55)},    /* Chatham Island Daylight Time (13:45) */
+   {"chast", TZ, POS(51)},     /* Chatham Island Time (12:45) */
+#if 0
+   ckhst
+#endif
+   {"ckt", TZ, POS(48)},       /* Cook Islands Time */
+   {"clst", DTZ, NEG(12)},     /* Chile Summer Time */
+   {"clt", TZ, NEG(16)},       /* Chile Time */
+#if 0
+   cost
+#endif
+   {"cot", TZ, NEG(20)},       /* Columbia Time */
+   {"cst", TZ, NEG(24)},       /* Central Standard Time */
+   {DCURRENT, RESERV, DTK_CURRENT},    /* "current" is always now */
+#if 0
+   cvst
+#endif
+   {"cvt", TZ, POS(28)},       /* Christmas Island Time (Indian Ocean) */
+   {"cxt", TZ, POS(28)},       /* Christmas Island Time (Indian Ocean) */
+   {"d", UNITS, DTK_DAY},      /* "day of month" for ISO input */
+   {"davt", TZ, POS(28)},      /* Davis Time (Antarctica) */
+   {"ddut", TZ, POS(40)},      /* Dumont-d'Urville Time (Antarctica) */
+   {"dec", MONTH, 12},
+   {"december", MONTH, 12},
+   {"dnt", TZ, POS(4)},        /* Dansk Normal Tid */
+   {"dow", RESERV, DTK_DOW},   /* day of week */
+   {"doy", RESERV, DTK_DOY},   /* day of year */
+   {"dst", DTZMOD, 6},
+#if 0
+   {"dusst", DTZ, POS(24)},    /* Dushanbe Summer Time */
+#endif
+   {"easst", DTZ, NEG(20)},    /* Easter Island Summer Time */
+   {"east", TZ, NEG(24)},      /* Easter Island Time */
+   {"eat", TZ, POS(12)},       /* East Africa Time */
+#if 0
+   {"east", DTZ, POS(16)},     /* Indian Antananarivo Savings Time */
+   {"eat", TZ, POS(12)},       /* Indian Antananarivo Time */
+   {"ect", TZ, NEG(16)},       /* Eastern Caribbean Time */
+   {"ect", TZ, NEG(20)},       /* Ecuador Time */
+#endif
+   {"edt", DTZ, NEG(16)},      /* Eastern Daylight Time */
+   {"eest", DTZ, POS(12)},     /* Eastern Europe Summer Time */
+   {"eet", TZ, POS(8)},        /* East. Europe, USSR Zone 1 */
+   {"eetdst", DTZ, POS(12)},   /* Eastern Europe Daylight Time */
+   {"egst", DTZ, POS(0)},      /* East Greenland Summer Time */
+   {"egt", TZ, NEG(4)},        /* East Greenland Time */
+#if 0
+   ehdt
+#endif
+   {EPOCH, RESERV, DTK_EPOCH}, /* "epoch" reserved for system epoch time */
+   {"est", TZ, NEG(20)},       /* Eastern Standard Time */
+   {"feb", MONTH, 2},
+   {"february", MONTH, 2},
+   {"fjst", DTZ, NEG(52)},     /* Fiji Summer Time (13 hour offset!) */
+   {"fjt", TZ, NEG(48)},       /* Fiji Time */
+   {"fkst", DTZ, NEG(12)},     /* Falkland Islands Summer Time */
+   {"fkt", TZ, NEG(8)},        /* Falkland Islands Time */
+#if 0
+   fnst
+   fnt
+#endif
+   {"fri", DOW, 5},
+   {"friday", DOW, 5},
+   {"fst", TZ, POS(4)},        /* French Summer Time */
+   {"fwt", DTZ, POS(8)},       /* French Winter Time  */
+   {"galt", TZ, NEG(24)},      /* Galapagos Time */
+   {"gamt", TZ, NEG(36)},      /* Gambier Time */
+   {"gest", DTZ, POS(20)},     /* Georgia Summer Time */
+   {"get", TZ, POS(16)},       /* Georgia Time */
+   {"gft", TZ, NEG(12)},       /* French Guiana Time */
+#if 0
+   ghst
+#endif
+   {"gilt", TZ, POS(48)},      /* Gilbert Islands Time */
+   {"gmt", TZ, POS(0)},        /* Greenwish Mean Time */
+   {"gst", TZ, POS(40)},       /* Guam Std Time, USSR Zone 9 */
+   {"gyt", TZ, NEG(16)},       /* Guyana Time */
+   {"h", UNITS, DTK_HOUR},     /* "hour" */
+#if 0
+   hadt
+   hast
+#endif
+   {"hdt", DTZ, NEG(36)},      /* Hawaii/Alaska Daylight Time */
+#if 0
+   hkst
+#endif
+   {"hkt", TZ, POS(32)},       /* Hong Kong Time */
+#if 0
+   {"hmt", TZ, POS(12)},       /* Hellas ? ? */
+   hovst
+   hovt
+#endif
+   {"hst", TZ, NEG(40)},       /* Hawaii Std Time */
+#if 0
+   hwt
+#endif
+   {"ict", TZ, POS(28)},       /* Indochina Time */
+   {"idle", TZ, POS(48)},      /* Intl. Date Line, East */
+   {"idlw", TZ, NEG(48)},      /* Intl. Date Line, West */
+#if 0
+   idt                         /* Israeli, Iran, Indian Daylight Time */
+#endif
+   {LATE, RESERV, DTK_LATE},   /* "infinity" reserved for "late time" */
+   {INVALID, RESERV, DTK_INVALID},     /* "invalid" reserved for bad time */
+   {"iot", TZ, POS(20)},       /* Indian Chagos Time */
+   {"irkst", DTZ, POS(36)},    /* Irkutsk Summer Time */
+   {"irkt", TZ, POS(32)},      /* Irkutsk Time */
+   {"irt", TZ, POS(14)},       /* Iran Time */
+#if 0
+   isst
+#endif
+   {"ist", TZ, POS(8)},        /* Israel */
+   {"it", TZ, POS(14)},        /* Iran Time */
+   {"j", UNITS, DTK_JULIAN},
+   {"jan", MONTH, 1},
+   {"january", MONTH, 1},
+   {"javt", TZ, POS(28)},      /* Java Time (07:00? see JT) */
+   {"jayt", TZ, POS(36)},      /* Jayapura Time (Indonesia) */
+   {"jd", UNITS, DTK_JULIAN},
+   {"jst", TZ, POS(36)},       /* Japan Std Time,USSR Zone 8 */
+   {"jt", TZ, POS(30)},        /* Java Time (07:30? see JAVT) */
+   {"jul", MONTH, 7},
+   {"julian", UNITS, DTK_JULIAN},
+   {"july", MONTH, 7},
+   {"jun", MONTH, 6},
+   {"june", MONTH, 6},
+   {"kdt", DTZ, POS(40)},      /* Korea Daylight Time */
+   {"kgst", DTZ, POS(24)},     /* Kyrgyzstan Summer Time */
+   {"kgt", TZ, POS(20)},       /* Kyrgyzstan Time */
+   {"kost", TZ, POS(48)},      /* Kosrae Time */
+   {"krast", DTZ, POS(28)},    /* Krasnoyarsk Summer Time */
+   {"krat", TZ, POS(32)},      /* Krasnoyarsk Standard Time */
+   {"kst", TZ, POS(36)},       /* Korea Standard Time */
+   {"lhdt", DTZ, POS(44)},     /* Lord Howe Daylight Time, Australia */
+   {"lhst", TZ, POS(42)},      /* Lord Howe Standard Time, Australia */
+   {"ligt", TZ, POS(40)},      /* From Melbourne, Australia */
+   {"lint", TZ, POS(56)},      /* Line Islands Time (Kiribati; +14
+                                * hours!) */
+   {"lkt", TZ, POS(24)},       /* Lanka Time */
+   {"m", UNITS, DTK_MONTH},    /* "month" for ISO input */
+   {"magst", DTZ, POS(48)},    /* Magadan Summer Time */
+   {"magt", TZ, POS(44)},      /* Magadan Time */
+   {"mar", MONTH, 3},
+   {"march", MONTH, 3},
+   {"mart", TZ, NEG(38)},      /* Marquesas Time */
+   {"mawt", TZ, POS(24)},      /* Mawson, Antarctica */
+   {"may", MONTH, 5},
+   {"mdt", DTZ, NEG(24)},      /* Mountain Daylight Time */
+   {"mest", DTZ, POS(8)},      /* Middle Europe Summer Time */
+   {"met", TZ, POS(4)},        /* Middle Europe Time */
+   {"metdst", DTZ, POS(8)},    /* Middle Europe Daylight Time */
+   {"mewt", TZ, POS(4)},       /* Middle Europe Winter Time */
+   {"mez", TZ, POS(4)},        /* Middle Europe Zone */
+   {"mht", TZ, POS(48)},       /* Kwajalein */
+   {"mm", UNITS, DTK_MINUTE},  /* "minute" for ISO input */
+   {"mmt", TZ, POS(26)},       /* Myannar Time */
+   {"mon", DOW, 1},
+   {"monday", DOW, 1},
+#if 0
+   most
+#endif
+   {"mpt", TZ, POS(40)},       /* North Mariana Islands Time */
+   {"msd", DTZ, POS(16)},      /* Moscow Summer Time */
+   {"msk", TZ, POS(12)},       /* Moscow Time */
+   {"mst", TZ, NEG(28)},       /* Mountain Standard Time */
+   {"mt", TZ, POS(34)},        /* Moluccas Time */
+   {"mut", TZ, POS(16)},       /* Mauritius Island Time */
+   {"mvt", TZ, POS(20)},       /* Maldives Island Time */
+   {"myt", TZ, POS(32)},       /* Malaysia Time */
+#if 0
+   ncst
+#endif
+   {"nct", TZ, POS(44)},       /* New Caledonia Time */
+   {"ndt", DTZ, NEG(10)},      /* Nfld. Daylight Time */
+   {"nft", TZ, NEG(14)},       /* Newfoundland Standard Time */
+   {"nor", TZ, POS(4)},        /* Norway Standard Time */
+   {"nov", MONTH, 11},
+   {"november", MONTH, 11},
+   {"novst", DTZ, POS(28)},    /* Novosibirsk Summer Time */
+   {"novt", TZ, POS(24)},      /* Novosibirsk Standard Time */
+   {NOW, RESERV, DTK_NOW},     /* current transaction time */
+   {"npt", TZ, POS(23)},       /* Nepal Standard Time (GMT-5:45) */
+   {"nst", TZ, NEG(14)},       /* Nfld. Standard Time */
+   {"nt", TZ, NEG(44)},        /* Nome Time */
+   {"nut", TZ, NEG(44)},       /* Niue Time */
+   {"nzdt", DTZ, POS(52)},     /* New Zealand Daylight Time */
+   {"nzst", TZ, POS(48)},      /* New Zealand Standard Time */
+   {"nzt", TZ, POS(48)},       /* New Zealand Time */
+   {"oct", MONTH, 10},
+   {"october", MONTH, 10},
+   {"omsst", DTZ, POS(28)},    /* Omsk Summer Time */
+   {"omst", TZ, POS(24)},      /* Omsk Time */
+   {"on", IGNORE_DTF, 0},      /* "on" (throwaway) */
+   {"pdt", DTZ, NEG(28)},      /* Pacific Daylight Time */
+#if 0
+   pest
+#endif
+   {"pet", TZ, NEG(20)},       /* Peru Time */
+   {"petst", DTZ, POS(52)},    /* Petropavlovsk-Kamchatski Summer Time */
+   {"pett", TZ, POS(48)},      /* Petropavlovsk-Kamchatski Time */
+   {"pgt", TZ, POS(40)},       /* Papua New Guinea Time */
+   {"phot", TZ, POS(52)},      /* Phoenix Islands (Kiribati) Time */
+#if 0
+   phst
+#endif
+   {"pht", TZ, POS(32)},       /* Phillipine Time */
+   {"pkt", TZ, POS(20)},       /* Pakistan Time */
+   {"pm", AMPM, PM},
+   {"pmdt", DTZ, NEG(8)},      /* Pierre & Miquelon Daylight Time */
+#if 0
+   pmst
+#endif
+   {"pont", TZ, POS(44)},      /* Ponape Time (Micronesia) */
+   {"pst", TZ, NEG(32)},       /* Pacific Standard Time */
+   {"pwt", TZ, POS(36)},       /* Palau Time */
+   {"pyst", DTZ, NEG(12)},     /* Paraguay Summer Time */
+   {"pyt", TZ, NEG(16)},       /* Paraguay Time */
+   {"ret", DTZ, POS(16)},      /* Reunion Island Time */
+   {"s", UNITS, DTK_SECOND},   /* "seconds" for ISO input */
+   {"sadt", DTZ, POS(42)},     /* S. Australian Dayl. Time */
+#if 0
+   samst
+   samt
+#endif
+   {"sast", TZ, POS(38)},      /* South Australian Std Time */
+   {"sat", DOW, 6},
+   {"saturday", DOW, 6},
+#if 0
+   sbt
+#endif
+   {"sct", DTZ, POS(16)},      /* Mahe Island Time */
+   {"sep", MONTH, 9},
+   {"sept", MONTH, 9},
+   {"september", MONTH, 9},
+   {"set", TZ, NEG(4)},        /* Seychelles Time ?? */
+#if 0
+   sgt
+#endif
+   {"sst", DTZ, POS(8)},       /* Swedish Summer Time */
+   {"sun", DOW, 0},
+   {"sunday", DOW, 0},
+   {"swt", TZ, POS(4)},        /* Swedish Winter Time */
+#if 0
+   syot
+#endif
+   {"t", ISOTIME, DTK_TIME},   /* Filler for ISO time fields */
+   {"tft", TZ, POS(20)},       /* Kerguelen Time */
+   {"that", TZ, NEG(40)},      /* Tahiti Time */
+   {"thu", DOW, 4},
+   {"thur", DOW, 4},
+   {"thurs", DOW, 4},
+   {"thursday", DOW, 4},
+   {"tjt", TZ, POS(20)},       /* Tajikistan Time */
+   {"tkt", TZ, NEG(40)},       /* Tokelau Time */
+   {"tmt", TZ, POS(20)},       /* Turkmenistan Time */
+   {TODAY, RESERV, DTK_TODAY}, /* midnight */
+   {TOMORROW, RESERV, DTK_TOMORROW},   /* tomorrow midnight */
+#if 0
+   tost
+#endif
+   {"tot", TZ, POS(52)},       /* Tonga Time */
+#if 0
+   tpt
+#endif
+   {"truk", TZ, POS(40)},      /* Truk Time */
+   {"tue", DOW, 2},
+   {"tues", DOW, 2},
+   {"tuesday", DOW, 2},
+   {"tvt", TZ, POS(48)},       /* Tuvalu Time */
+#if 0
+   uct
+#endif
+   {"ulast", DTZ, POS(36)},    /* Ulan Bator Summer Time */
+   {"ulat", TZ, POS(32)},      /* Ulan Bator Time */
+   {"undefined", RESERV, DTK_INVALID}, /* pre-v6.1 invalid time */
+   {"ut", TZ, POS(0)},
+   {"utc", TZ, POS(0)},
+   {"uyst", DTZ, NEG(8)},      /* Uruguay Summer Time */
+   {"uyt", TZ, NEG(12)},       /* Uruguay Time */
+   {"uzst", DTZ, POS(24)},     /* Uzbekistan Summer Time */
+   {"uzt", TZ, POS(20)},       /* Uzbekistan Time */
+   {"vet", TZ, NEG(16)},       /* Venezuela Time */
+   {"vlast", DTZ, POS(44)},    /* Vladivostok Summer Time */
+   {"vlat", TZ, POS(40)},      /* Vladivostok Time */
+#if 0
+   vust
+#endif
+   {"vut", TZ, POS(44)},       /* Vanuata Time */
+   {"wadt", DTZ, POS(32)},     /* West Australian DST */
+   {"wakt", TZ, POS(48)},      /* Wake Time */
+#if 0
+   warst
+#endif
+   {"wast", TZ, POS(28)},      /* West Australian Std Time */
+   {"wat", TZ, NEG(4)},        /* West Africa Time */
+   {"wdt", DTZ, POS(36)},      /* West Australian DST */
+   {"wed", DOW, 3},
+   {"wednesday", DOW, 3},
+   {"weds", DOW, 3},
+   {"west", DTZ, POS(4)},      /* Western Europe Summer Time */
+   {"wet", TZ, POS(0)},        /* Western Europe */
+   {"wetdst", DTZ, POS(4)},    /* Western Europe Daylight Savings Time */
+   {"wft", TZ, POS(48)},       /* Wallis and Futuna Time */
+   {"wgst", DTZ, NEG(8)},      /* West Greenland Summer Time */
+   {"wgt", TZ, NEG(12)},       /* West Greenland Time */
+   {"wst", TZ, POS(32)},       /* West Australian Standard Time */
+   {"y", UNITS, DTK_YEAR},     /* "year" for ISO input */
+   {"yakst", DTZ, POS(40)},    /* Yakutsk Summer Time */
+   {"yakt", TZ, POS(36)},      /* Yakutsk Time */
+   {"yapt", TZ, POS(40)},      /* Yap Time (Micronesia) */
+   {"ydt", DTZ, NEG(32)},      /* Yukon Daylight Time */
+   {"yekst", DTZ, POS(24)},    /* Yekaterinburg Summer Time */
+   {"yekt", TZ, POS(20)},      /* Yekaterinburg Time */
+   {YESTERDAY, RESERV, DTK_YESTERDAY}, /* yesterday midnight */
+   {"yst", TZ, NEG(36)},       /* Yukon Standard Time */
+   {"z", TZ, POS(0)},          /* time zone tag per ISO-8601 */
+   {"zp4", TZ, NEG(16)},       /* UTC +4  hours. */
+   {"zp5", TZ, NEG(20)},       /* UTC +5  hours. */
+   {"zp6", TZ, NEG(24)},       /* UTC +6  hours. */
+   {ZULU, TZ, POS(0)},         /* UTC */
+};
+
+static unsigned int szdatetktbl = sizeof datetktbl / sizeof datetktbl[0];
+
+static datetkn    *datecache[MAXDATEFIELDS] = {NULL};
+
+char       *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL};
+
+char       *days[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", NULL};
+
+#ifndef HAVE_RINT
+
+/* @(#)s_rint.c 5.1 93/09/24 */
+/*
+ * ====================================================
+ * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
+ *
+ * Developed at SunPro, a Sun Microsystems, Inc. business.
+ * Permission to use, copy, modify, and distribute this
+ * software is freely granted, provided that this notice
+ * is preserved.
+ * ====================================================
+ */
+
+/*
+ * rint(x)
+ * Return x rounded to integral value according to the prevailing
+ * rounding mode.
+ * Method:
+ *     Using floating addition.
+ * Exception:
+ *     Inexact flag raised if x not equal to rint(x).
+ */
+
+static const double one = 1.0,
+           TWO52[2] = {
+   4.50359962737049600000e+15, /* 0x43300000, 0x00000000 */
+   -4.50359962737049600000e+15,    /* 0xC3300000, 0x00000000 */
+};
+
+double
+rint(double x)
+{
+   int         i0,
+               n0,
+               j0,
+               sx;
+   unsigned    i,
+               i1;
+   double      w,
+               t;
+
+   n0 = (*((int *) &one) >> 29) ^ 1;
+   i0 = *(n0 + (int *) &x);
+   sx = (i0 >> 31) & 1;
+   i1 = *(1 - n0 + (int *) &x);
+   j0 = ((i0 >> 20) & 0x7ff) - 0x3ff;
+   if (j0 < 20)
+   {
+       if (j0 < 0)
+       {
+           if (((i0 & 0x7fffffff) | i1) == 0)
+               return x;
+           i1 |= (i0 & 0x0fffff);
+           i0 &= 0xfffe0000;
+           i0 |= ((i1 | -i1) >> 12) & 0x80000;
+           *(n0 + (int *) &x) = i0;
+           w = TWO52[sx] + x;
+           t = w - TWO52[sx];
+           i0 = *(n0 + (int *) &t);
+           *(n0 + (int *) &t) = (i0 & 0x7fffffff) | (sx << 31);
+           return t;
+       }
+       else
+       {
+           i = (0x000fffff) >> j0;
+           if (((i0 & i) | i1) == 0)
+               return x;       /* x is integral */
+           i >>= 1;
+           if (((i0 & i) | i1) != 0)
+           {
+               if (j0 == 19)
+                   i1 = 0x40000000;
+               else
+                   i0 = (i0 & (~i)) | ((0x20000) >> j0);
+           }
+       }
+   }
+   else if (j0 > 51)
+   {
+       if (j0 == 0x400)
+           return x + x;       /* inf or NaN */
+       else
+           return x;           /* x is integral */
+   }
+   else
+   {
+       i = ((unsigned) (0xffffffff)) >> (j0 - 20);
+       if ((i1 & i) == 0)
+           return x;           /* x is integral */
+       i >>= 1;
+       if ((i1 & i) != 0)
+           i1 = (i1 & (~i)) | ((0x40000000) >> (j0 - 20));
+   }
+   *(n0 + (int *) &x) = i0;
+   *(1 - n0 + (int *) &x) = i1;
+   w = TWO52[sx] + x;
+   return w - TWO52[sx];
+}
+
+#endif   /* !HAVE_RINT */
+
+static datetkn *
+datebsearch(char *key, datetkn *base, unsigned int nel)
+{
+        datetkn    *last = base + nel - 1,
+                   *position;
+        int                     result;
+
+   while (last >= base)
+        {
+                   position = base + ((last - base) >> 1);
+                   result = key[0] - position->token[0];
+                   if (result == 0)
+                   {
+                           result = strncmp(key, position->token, TOKMAXLEN);
+                           if (result == 0)
+                           return position;
+                   }
+                   if (result < 0)
+                     last = position - 1;
+                   else
+                      base = position + 1;
+           }
+           return NULL;
+}
+
+/*
+ * Calendar time to Julian date conversions.
+ * Julian date is commonly used in astronomical applications,
+ * since it is numerically accurate and computationally simple.
+ * The algorithms here will accurately convert between Julian day
+ * and calendar date for all non-negative Julian days
+ * (i.e. from Nov 24, -4713 on).
+ *
+ * These routines will be used by other date/time packages
+ * - thomas 97/02/25
+ *
+ * Rewritten to eliminate overflow problems. This now allows the
+ * routines to work correctly for all Julian day counts from
+ * 0 to 2147483647  (Nov 24, -4713 to Jun 3, 5874898) assuming
+ * a 32-bit integer. Longer types should also work to the limits
+ * of their precision.
+ */
+
+int
+date2j(int y, int m, int d)
+{
+   int         julian;
+   int         century;
+
+   if (m > 2) {
+       m += 1;
+       y += 4800;
+   } else {
+       m += 13;
+       y += 4799;
+   }
+
+   century = y/100;
+   julian  = y*365 - 32167;
+   julian += y/4 - century + century/4;
+   julian += 7834*m/256 + d;
+
+   return julian;
+}  /* date2j() */
+
+void
+j2date(int jd, int *year, int *month, int *day)
+{
+   unsigned int        julian;
+   unsigned int        quad;
+   unsigned int        extra;
+   int         y;
+
+   julian = jd;
+   julian += 32044;
+   quad = julian/146097;
+   extra = (julian - quad*146097)*4 + 3;
+   julian += 60 + quad*3 + extra/146097;
+   quad = julian/1461;
+   julian -= quad*1461;
+   y = julian * 4 / 1461;
+   julian = ((y != 0) ? ((julian + 305) % 365) : ((julian + 306) % 366))
+       + 123;
+   y += quad*4;
+   *year = y - 4800;
+   quad = julian * 2141 / 65536;
+   *day = julian - 7834*quad/256;
+   *month = (quad + 10) % 12 + 1;
+
+   return;
+}  /* j2date() */
+
+int
+j2day(int date)
+{
+        unsigned int day;
+
+        day = date;
+        day += 1;
+        day %= 7;
+        return (int) day;
+}       /*j2day() */
+
+/* DecodeSpecial()
+ * Decode text string using lookup table.
+ * Implement a cache lookup since it is likely that dates
+ * will be related in format.
+ */
+static int
+DecodeSpecial(int field, char *lowtoken, int *val)
+{
+   int         type;
+   datetkn    *tp;
+
+   if ((datecache[field] != NULL)
+       && (strncmp(lowtoken, datecache[field]->token, TOKMAXLEN) == 0))
+       tp = datecache[field];
+   else
+   {
+       tp = NULL;
+       if (!tp)
+           tp = datebsearch(lowtoken, datetktbl, szdatetktbl);
+   }
+   datecache[field] = tp;
+   if (tp == NULL)
+   {
+       type = UNKNOWN_FIELD;
+       *val = 0;
+   }
+   else
+   {
+       type = tp->type;
+       switch (type)
+       {
+           case TZ:
+           case DTZ:
+           case DTZMOD:
+               *val = FROMVAL(tp);
+               break;
+
+           default:
+               *val = tp->value;
+               break;
+       }
+   }
+
+   return type;
+}  /* DecodeSpecial() */
+
+/* EncodeDateOnly()
+ * Encode date as local time.
+ */
+int
+EncodeDateOnly(struct tm * tm, int style, char *str, bool EuroDates)
+{
+   if ((tm->tm_mon < 1) || (tm->tm_mon > 12))
+       return -1;
+
+   switch (style)
+   {
+       case USE_ISO_DATES:
+           /* compatible with ISO date formats */
+           if (tm->tm_year > 0)
+               sprintf(str, "%04d-%02d-%02d",
+                       tm->tm_year, tm->tm_mon, tm->tm_mday);
+           else
+               sprintf(str, "%04d-%02d-%02d %s",
+                     -(tm->tm_year - 1), tm->tm_mon, tm->tm_mday, "BC");
+           break;
+
+       case USE_SQL_DATES:
+           /* compatible with Oracle/Ingres date formats */
+           if (EuroDates)
+               sprintf(str, "%02d/%02d", tm->tm_mday, tm->tm_mon);
+           else
+               sprintf(str, "%02d/%02d", tm->tm_mon, tm->tm_mday);
+           if (tm->tm_year > 0)
+               sprintf((str + 5), "/%04d", tm->tm_year);
+           else
+               sprintf((str + 5), "/%04d %s", -(tm->tm_year - 1), "BC");
+           break;
+
+       case USE_GERMAN_DATES:
+           /* German-style date format */
+           sprintf(str, "%02d.%02d", tm->tm_mday, tm->tm_mon);
+           if (tm->tm_year > 0)
+               sprintf((str + 5), ".%04d", tm->tm_year);
+           else
+               sprintf((str + 5), ".%04d %s", -(tm->tm_year - 1), "BC");
+           break;
+
+       case USE_POSTGRES_DATES:
+       default:
+           /* traditional date-only style for Postgres */
+           if (EuroDates)
+               sprintf(str, "%02d-%02d", tm->tm_mday, tm->tm_mon);
+           else
+               sprintf(str, "%02d-%02d", tm->tm_mon, tm->tm_mday);
+           if (tm->tm_year > 0)
+               sprintf((str + 5), "-%04d", tm->tm_year);
+           else
+               sprintf((str + 5), "-%04d %s", -(tm->tm_year - 1), "BC");
+           break;
+   }
+
+   return TRUE;
+}  /* EncodeDateOnly() */
+
+static void
+TrimTrailingZeros(char *str)
+{
+    int                     len = strlen(str);
+       
+    /* chop off trailing zeros... but leave at least 2 fractional digits */
+    while ((*(str + len - 1) == '0') && (*(str + len - 3) != '.'))
+    {
+          len--;
+          *(str + len) = '\0';
+    }
+}
+
+/* EncodeDateTime()
+ * Encode date and time interpreted as local time.
+ * Support several date styles:
+ * Postgres - day mon hh:mm:ss yyyy tz
+ * SQL - mm/dd/yyyy hh:mm:ss.ss tz
+ * ISO - yyyy-mm-dd hh:mm:ss+/-tz
+ * German - dd.mm.yyyy hh:mm:ss tz
+ * Variants (affects order of month and day for Postgres and SQL styles):
+ * US - mm/dd/yyyy
+ * European - dd/mm/yyyy
+ */
+int
+EncodeDateTime(struct tm * tm, fsec_t fsec, int *tzp, char **tzn, int style, char *str, bool EuroDates)
+{
+   int         day,
+               hour,
+               min;
+
+   switch (style)
+   {
+       case USE_ISO_DATES:
+           /* Compatible with ISO-8601 date formats */
+
+           sprintf(str, "%04d-%02d-%02d %02d:%02d",
+                 ((tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1)),
+                   tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min);
+
+           /*
+            * Print fractional seconds if any.  The field widths here should
+            * be at least equal to MAX_TIMESTAMP_PRECISION.
+            *
+            * In float mode, don't print fractional seconds before 1 AD,
+            * since it's unlikely there's any precision left ...
+            */
+#ifdef HAVE_INT64_TIMESTAMP
+           if (fsec != 0)
+           {
+               sprintf((str + strlen(str)), ":%02d.%06d", tm->tm_sec, fsec);
+#else
+           if ((fsec != 0) && (tm->tm_year > 0))
+           {
+               sprintf((str + strlen(str)), ":%09.6f", tm->tm_sec + fsec);
+#endif
+               TrimTrailingZeros(str);
+           }
+           else
+               sprintf((str + strlen(str)), ":%02d", tm->tm_sec);
+
+           if (tm->tm_year <= 0)
+               sprintf((str + strlen(str)), " BC");
+
+           /*
+            * tzp == NULL indicates that we don't want *any* time zone
+            * info in the output string. *tzn != NULL indicates that we
+            * have alpha time zone info available. tm_isdst != -1
+            * indicates that we have a valid time zone translation.
+            */
+           if ((tzp != NULL) && (tm->tm_isdst >= 0))
+           {
+               hour = -(*tzp / 3600);
+               min = ((abs(*tzp) / 60) % 60);
+               sprintf((str + strlen(str)), ((min != 0) ? "%+03d:%02d" : "%+03d"), hour, min);
+           }
+           break;
+
+       case USE_SQL_DATES:
+           /* Compatible with Oracle/Ingres date formats */
+
+           if (EuroDates)
+               sprintf(str, "%02d/%02d", tm->tm_mday, tm->tm_mon);
+           else
+               sprintf(str, "%02d/%02d", tm->tm_mon, tm->tm_mday);
+
+           sprintf((str + 5), "/%04d %02d:%02d",
+                 ((tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1)),
+                   tm->tm_hour, tm->tm_min);
+
+           /*
+            * Print fractional seconds if any.  The field widths here should
+            * be at least equal to MAX_TIMESTAMP_PRECISION.
+            *
+            * In float mode, don't print fractional seconds before 1 AD,
+            * since it's unlikely there's any precision left ...
+            */
+#ifdef HAVE_INT64_TIMESTAMP
+           if (fsec != 0)
+           {
+               sprintf((str + strlen(str)), ":%02d.%06d", tm->tm_sec, fsec);
+#else
+           if ((fsec != 0) && (tm->tm_year > 0))
+           {
+               sprintf((str + strlen(str)), ":%09.6f", tm->tm_sec + fsec);
+#endif
+               TrimTrailingZeros(str);
+           }
+           else
+               sprintf((str + strlen(str)), ":%02d", tm->tm_sec);
+
+           if (tm->tm_year <= 0)
+               sprintf((str + strlen(str)), " BC");
+
+           if ((tzp != NULL) && (tm->tm_isdst >= 0))
+           {
+               if (*tzn != NULL)
+                   sprintf((str + strlen(str)), " %.*s", MAXTZLEN, *tzn);
+               else
+               {
+                   hour = -(*tzp / 3600);
+                   min = ((abs(*tzp) / 60) % 60);
+                   sprintf((str + strlen(str)), ((min != 0) ? "%+03d:%02d" : "%+03d"), hour, min);
+               }
+           }
+           break;
+
+       case USE_GERMAN_DATES:
+           /* German variant on European style */
+
+           sprintf(str, "%02d.%02d", tm->tm_mday, tm->tm_mon);
+
+           sprintf((str + 5), ".%04d %02d:%02d",
+                 ((tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1)),
+                   tm->tm_hour, tm->tm_min);
+
+           /*
+            * Print fractional seconds if any.  The field widths here should
+            * be at least equal to MAX_TIMESTAMP_PRECISION.
+            *
+            * In float mode, don't print fractional seconds before 1 AD,
+            * since it's unlikely there's any precision left ...
+            */
+#ifdef HAVE_INT64_TIMESTAMP
+           if (fsec != 0)
+           {
+               sprintf((str + strlen(str)), ":%02d.%06d", tm->tm_sec, fsec);
+#else
+           if ((fsec != 0) && (tm->tm_year > 0))
+           {
+               sprintf((str + strlen(str)), ":%09.6f", tm->tm_sec + fsec);
+#endif
+               TrimTrailingZeros(str);
+           }
+           else
+               sprintf((str + strlen(str)), ":%02d", tm->tm_sec);
+
+           if (tm->tm_year <= 0)
+               sprintf((str + strlen(str)), " BC");
+
+           if ((tzp != NULL) && (tm->tm_isdst >= 0))
+           {
+               if (*tzn != NULL)
+                   sprintf((str + strlen(str)), " %.*s", MAXTZLEN, *tzn);
+               else
+               {
+                   hour = -(*tzp / 3600);
+                   min = ((abs(*tzp) / 60) % 60);
+                   sprintf((str + strlen(str)), ((min != 0) ? "%+03d:%02d" : "%+03d"), hour, min);
+               }
+           }
+           break;
+
+       case USE_POSTGRES_DATES:
+       default:
+           /* Backward-compatible with traditional Postgres abstime dates */
+
+           day = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday);
+           tm->tm_wday = j2day(day);
+
+           strncpy(str, days[tm->tm_wday], 3);
+           strcpy((str + 3), " ");
+
+           if (EuroDates)
+               sprintf((str + 4), "%02d %3s", tm->tm_mday, months[tm->tm_mon - 1]);
+           else
+               sprintf((str + 4), "%3s %02d", months[tm->tm_mon - 1], tm->tm_mday);
+
+           sprintf((str + 10), " %02d:%02d", tm->tm_hour, tm->tm_min);
+
+           /*
+            * Print fractional seconds if any.  The field widths here should
+            * be at least equal to MAX_TIMESTAMP_PRECISION.
+            *
+            * In float mode, don't print fractional seconds before 1 AD,
+            * since it's unlikely there's any precision left ...
+            */
+#ifdef HAVE_INT64_TIMESTAMP
+           if (fsec != 0)
+           {
+               sprintf((str + strlen(str)), ":%02d.%06d", tm->tm_sec, fsec);
+#else
+           if ((fsec != 0) && (tm->tm_year > 0))
+           {
+               sprintf((str + strlen(str)), ":%09.6f", tm->tm_sec + fsec);
+#endif
+               TrimTrailingZeros(str);
+           }
+           else
+               sprintf((str + strlen(str)), ":%02d", tm->tm_sec);
+
+           sprintf((str + strlen(str)), " %04d",
+                ((tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1)));
+           if (tm->tm_year <= 0)
+               sprintf((str + strlen(str)), " BC");
+
+           if ((tzp != NULL) && (tm->tm_isdst >= 0))
+           {
+               if (*tzn != NULL)
+                   sprintf((str + strlen(str)), " %.*s", MAXTZLEN, *tzn);
+               else
+               {
+                   /*
+                    * We have a time zone, but no string version. Use the
+                    * numeric form, but be sure to include a leading
+                    * space to avoid formatting something which would be
+                    * rejected by the date/time parser later. - thomas
+                    * 2001-10-19
+                    */
+                   hour = -(*tzp / 3600);
+                   min = ((abs(*tzp) / 60) % 60);
+                   sprintf((str + strlen(str)), ((min != 0) ? " %+03d:%02d" : " %+03d"), hour, min);
+               }
+           }
+           break;
+   }
+
+   return TRUE;
+}  /* EncodeDateTime() */
+
+void
+GetEpochTime(struct tm * tm)
+{
+   struct tm  *t0;
+   time_t      epoch = 0;
+
+   t0 = gmtime(&epoch);
+
+   tm->tm_year = t0->tm_year;
+   tm->tm_mon = t0->tm_mon;
+   tm->tm_mday = t0->tm_mday;
+   tm->tm_hour = t0->tm_hour;
+   tm->tm_min = t0->tm_min;
+   tm->tm_sec = t0->tm_sec;
+
+   if (tm->tm_year < 1900)
+       tm->tm_year += 1900;
+   tm->tm_mon++;
+
+   return;
+}  /* GetEpochTime() */
+
+static void
+abstime2tm(AbsoluteTime _time, int *tzp, struct tm * tm, char **tzn)
+{
+   time_t      time = (time_t) _time;
+   struct tm  *tx;
+
+   if (tzp != NULL)
+       tx = localtime((time_t *) &time);
+   else
+       tx = gmtime((time_t *) &time);
+
+   tm->tm_year = tx->tm_year + 1900;
+   tm->tm_mon = tx->tm_mon + 1;
+   tm->tm_mday = tx->tm_mday;
+   tm->tm_hour = tx->tm_hour;
+   tm->tm_min = tx->tm_min;
+   tm->tm_sec = tx->tm_sec;
+   tm->tm_isdst = tx->tm_isdst;
+
+#if defined(HAVE_TM_ZONE)
+   tm->tm_gmtoff = tx->tm_gmtoff;
+   tm->tm_zone = tx->tm_zone;
+
+   if (tzp != NULL)
+   {
+       /*
+        * We have a brute force time zone per SQL99? Then use it without
+        * change since we have already rotated to the time zone.
+        */
+       *tzp = -tm->tm_gmtoff;      /* tm_gmtoff is Sun/DEC-ism */
+       /*
+        * XXX FreeBSD man pages indicate that this should work - tgl
+        * 97/04/23
+        */
+       if (tzn != NULL)
+       {
+               /*
+                * Copy no more than MAXTZLEN bytes of timezone to tzn, in
+                * case it contains an error message, which doesn't fit in
+                * the buffer
+                */
+               StrNCpy(*tzn, tm->tm_zone, MAXTZLEN + 1);
+               if (strlen(tm->tm_zone) > MAXTZLEN)
+                   elog(WARNING, "Invalid timezone \'%s\'",
+                        tm->tm_zone);
+       }
+   }
+   else
+       tm->tm_isdst = -1;
+#elif defined(HAVE_INT_TIMEZONE)
+   if (tzp != NULL)
+   {
+           *tzp = ((tm->tm_isdst > 0) ? (TIMEZONE_GLOBAL - 3600) : TIMEZONE_GLOBAL);
+
+           if (tzn != NULL)
+           {
+               /*
+                * Copy no more than MAXTZLEN bytes of timezone to tzn, in
+                * case it contains an error message, which doesn't fit in
+                * the buffer
+                */
+               StrNCpy(*tzn, tzname[tm->tm_isdst], MAXTZLEN + 1);
+               if (strlen(tzname[tm->tm_isdst]) > MAXTZLEN)
+                   elog(WARNING, "Invalid timezone \'%s\'",
+                        tzname[tm->tm_isdst]);
+           }
+   }
+   else
+       tm->tm_isdst = -1;
+#else                          /* not (HAVE_TM_ZONE || HAVE_INT_TIMEZONE) */
+   if (tzp != NULL)
+   {
+       /* default to UTC */
+       *tzp = 0;
+       if (tzn != NULL)
+           *tzn = NULL;
+   }
+   else
+       tm->tm_isdst = -1;
+#endif
+}
+
+static void
+GetCurrentDateTime(struct tm * tm)
+{
+   int         tz;
+
+   abstime2tm(time(NULL), &tz, tm, NULL);
+}
+
+/* DetermineLocalTimeZone()
+ *
+ * Given a struct tm in which tm_year, tm_mon, tm_mday, tm_hour, tm_min, and
+ * tm_sec fields are set, attempt to determine the applicable local zone
+ * (ie, regular or daylight-savings time) at that time.  Set the struct tm's
+ * tm_isdst field accordingly, and return the actual timezone offset.
+ *
+ * This subroutine exists to centralize uses of mktime() and defend against
+ * mktime() bugs/restrictions on various platforms.  This should be
+ * the *only* call of mktime() in the backend.
+ */
+static int
+DetermineLocalTimeZone(struct tm * tm)
+{
+   int         tz;
+
+   if (IS_VALID_UTIME(tm->tm_year, tm->tm_mon, tm->tm_mday))
+   {
+#if defined(HAVE_TM_ZONE) || defined(HAVE_INT_TIMEZONE)
+
+       /*
+        * Some buggy mktime() implementations may change the
+        * year/month/day when given a time right at a DST boundary.  To
+        * prevent corruption of the caller's data, give mktime() a
+        * copy...
+        */
+       struct tm   tt,
+                  *tmp = &tt;
+
+       *tmp = *tm;
+       /* change to Unix conventions for year/month */
+       tmp->tm_year -= 1900;
+       tmp->tm_mon -= 1;
+
+       /* indicate timezone unknown */
+       tmp->tm_isdst = -1;
+
+       if (mktime(tmp) != ((time_t) -1) &&
+           tmp->tm_isdst >= 0)
+       {
+           /* mktime() succeeded, trust its result */
+           tm->tm_isdst = tmp->tm_isdst;
+
+#if defined(HAVE_TM_ZONE)
+           /* tm_gmtoff is Sun/DEC-ism */
+           tz = -(tmp->tm_gmtoff);
+#elif defined(HAVE_INT_TIMEZONE)
+           tz = ((tmp->tm_isdst > 0) ? (TIMEZONE_GLOBAL - 3600) : TIMEZONE_GLOBAL);
+#endif   /* HAVE_INT_TIMEZONE */
+       }
+       else
+       {
+           /*
+            * We have a buggy (not to say deliberately brain damaged)
+            * mktime().  Work around it by using localtime() instead.
+            *
+            * First, generate the time_t value corresponding to the given
+            * y/m/d/h/m/s taken as GMT time.  This will not overflow (at
+            * least not for time_t taken as signed) because of the range
+            * check we did above.
+            */
+           long        day,
+                       mysec,
+                       locsec,
+                       delta1,
+                       delta2;
+           time_t      mytime;
+
+           day = (date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) -
+                  date2j(1970, 1, 1));
+           mysec = tm->tm_sec + (tm->tm_min + (day * 24 + tm->tm_hour) * 60) * 60;
+           mytime = (time_t) mysec;
+
+           /*
+            * Use localtime to convert that time_t to broken-down time,
+            * and reassemble to get a representation of local time.
+            */
+           tmp = localtime(&mytime);
+           day = (date2j(tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday) -
+                  date2j(1970, 1, 1));
+           locsec = tmp->tm_sec + (tmp->tm_min + (day * 24 + tmp->tm_hour) * 60) * 60;
+
+           /*
+            * The local time offset corresponding to that GMT time is now
+            * computable as mysec - locsec.
+            */
+           delta1 = mysec - locsec;
+
+           /*
+            * However, if that GMT time and the local time we are
+            * actually interested in are on opposite sides of a
+            * daylight-savings-time transition, then this is not the time
+            * offset we want.  So, adjust the time_t to be what we think
+            * the GMT time corresponding to our target local time is, and
+            * repeat the localtime() call and delta calculation.  We may
+            * have to do it twice before we have a trustworthy delta.
+            *
+            * Note: think not to put a loop here, since if we've been given
+            * an "impossible" local time (in the gap during a
+            * spring-forward transition) we'd never get out of the loop.
+            * Twice is enough to give the behavior we want, which is that
+            * "impossible" times are taken as standard time, while at a
+            * fall-back boundary ambiguous times are also taken as
+            * standard.
+            */
+           mysec += delta1;
+           mytime = (time_t) mysec;
+           tmp = localtime(&mytime);
+           day = (date2j(tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday) -
+                  date2j(1970, 1, 1));
+           locsec = tmp->tm_sec + (tmp->tm_min + (day * 24 + tmp->tm_hour) * 60) * 60;
+           delta2 = mysec - locsec;
+           if (delta2 != delta1)
+           {
+               mysec += (delta2 - delta1);
+               mytime = (time_t) mysec;
+               tmp = localtime(&mytime);
+               day = (date2j(tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday) -
+                      date2j(1970, 1, 1));
+               locsec = tmp->tm_sec + (tmp->tm_min + (day * 24 + tmp->tm_hour) * 60) * 60;
+               delta2 = mysec - locsec;
+           }
+           tm->tm_isdst = tmp->tm_isdst;
+           tz = (int) delta2;
+       }
+#else                          /* not (HAVE_TM_ZONE || HAVE_INT_TIMEZONE) */
+       /* Assume UTC if no system timezone info available */
+       tm->tm_isdst = 0;
+       tz = 0;
+#endif
+   }
+   else
+   {
+       /* Given date is out of range, so assume UTC */
+       tm->tm_isdst = 0;
+       tz = 0;
+   }
+
+   return tz;
+}
+
+static void
+dt2time(double jd, int *hour, int *min, int *sec, fsec_t *fsec)
+{
+#ifdef HAVE_INT64_TIMESTAMP
+   int64           time;
+
+#else
+   double          time;
+#endif
+
+   time = jd;
+#ifdef HAVE_INT64_TIMESTAMP
+        *hour = (time / INT64CONST(3600000000));
+        time -= ((*hour) * INT64CONST(3600000000));
+        *min = (time / INT64CONST(60000000));
+        time -= ((*min) * INT64CONST(60000000));
+        *sec = (time / INT64CONST(1000000));
+        *fsec = (time - (*sec * INT64CONST(1000000)));
+#else
+        *hour = (time / 3600);
+        time -= ((*hour) * 3600);
+   *min = (time / 60);
+        time -= ((*min) * 60);
+        *sec = time;
+        *fsec = JROUND(time - *sec);
+#endif
+        return;
+}       /* dt2time() */
+
+                                   
+               
+/* DecodeNumberField()
+ * Interpret numeric string as a concatenated date or time field.
+ * Use the context of previously decoded fields to help with
+ * the interpretation.
+ */
+static int
+DecodeNumberField(int len, char *str, int fmask,
+               int *tmask, struct tm * tm, fsec_t *fsec, int *is2digits, bool EuroDates)
+{
+   char       *cp;
+
+   /*
+    * Have a decimal point? Then this is a date or something with a
+    * seconds field...
+    */
+   if ((cp = strchr(str, '.')) != NULL)
+   {
+#ifdef HAVE_INT64_TIMESTAMP
+       char        fstr[MAXDATELEN + 1];
+
+       /*
+        * OK, we have at most six digits to care about. Let's construct a
+        * string and then do the conversion to an integer.
+        */
+       strcpy(fstr, (cp + 1));
+       strcpy((fstr + strlen(fstr)), "000000");
+       *(fstr + 6) = '\0';
+       *fsec = strtol(fstr, NULL, 10);
+#else
+       *fsec = strtod(cp, NULL);
+#endif
+       *cp = '\0';
+       len = strlen(str);
+   }
+   /* No decimal point and no complete date yet? */
+   else if ((fmask & DTK_DATE_M) != DTK_DATE_M)
+   {
+       /* yyyymmdd? */
+       if (len == 8)
+       {
+           *tmask = DTK_DATE_M;
+
+           tm->tm_mday = atoi(str + 6);
+           *(str + 6) = '\0';
+           tm->tm_mon = atoi(str + 4);
+           *(str + 4) = '\0';
+           tm->tm_year = atoi(str + 0);
+
+           return DTK_DATE;
+       }
+       /* yymmdd? */
+       else if (len == 6)
+       {
+           *tmask = DTK_DATE_M;
+           tm->tm_mday = atoi(str + 4);
+           *(str + 4) = '\0';
+           tm->tm_mon = atoi(str + 2);
+           *(str + 2) = '\0';
+           tm->tm_year = atoi(str + 0);
+           *is2digits = TRUE;
+
+           return DTK_DATE;
+       }
+       /* yyddd? */
+       else if (len == 5)
+       {
+           *tmask = DTK_DATE_M;
+           tm->tm_mday = atoi(str + 2);
+           *(str + 2) = '\0';
+           tm->tm_mon = 1;
+           tm->tm_year = atoi(str + 0);
+           *is2digits = TRUE;
+
+           return DTK_DATE;
+       }
+   }
+
+   /* not all time fields are specified? */
+   if ((fmask & DTK_TIME_M) != DTK_TIME_M)
+   {
+       /* hhmmss */
+       if (len == 6)
+       {
+           *tmask = DTK_TIME_M;
+           tm->tm_sec = atoi(str + 4);
+           *(str + 4) = '\0';
+           tm->tm_min = atoi(str + 2);
+           *(str + 2) = '\0';
+           tm->tm_hour = atoi(str + 0);
+
+           return DTK_TIME;
+       }
+       /* hhmm? */
+       else if (len == 4)
+       {
+           *tmask = DTK_TIME_M;
+           tm->tm_sec = 0;
+           tm->tm_min = atoi(str + 2);
+           *(str + 2) = '\0';
+           tm->tm_hour = atoi(str + 0);
+
+           return DTK_TIME;
+       }
+   }
+
+   return -1;
+}  /* DecodeNumberField() */
+
+
+/* DecodeNumber()
+ * Interpret plain numeric field as a date value in context.
+ */
+static int
+DecodeNumber(int flen, char *str, int fmask,
+            int *tmask, struct tm * tm, fsec_t *fsec, int *is2digits, bool EuroDates)
+{
+   int         val;
+   char       *cp;
+
+   *tmask = 0;
+
+   val = strtol(str, &cp, 10);
+   if (cp == str)
+       return -1;
+
+   if (*cp == '.')
+   {
+       /*
+        * More than two digits? Then could be a date or a run-together
+        * time: 2001.360 20011225 040506.789
+        */
+       if ((cp - str) > 2)
+           return DecodeNumberField(flen, str, (fmask | DTK_DATE_M),
+                                    tmask, tm, fsec, is2digits, EuroDates);
+
+       *fsec = strtod(cp, &cp);
+       if (*cp != '\0')
+           return -1;
+   }
+   else if (*cp != '\0')
+       return -1;
+
+   /* Special case day of year? */
+   if ((flen == 3) && (fmask & DTK_M(YEAR))
+       && ((val >= 1) && (val <= 366)))
+   {
+       *tmask = (DTK_M(DOY) | DTK_M(MONTH) | DTK_M(DAY));
+       tm->tm_yday = val;
+       j2date((date2j(tm->tm_year, 1, 1) + tm->tm_yday - 1),
+              &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
+   }
+
+   /***
+    * Enough digits to be unequivocal year? Used to test for 4 digits or
+    * more, but we now test first for a three-digit doy so anything
+    * bigger than two digits had better be an explicit year.
+    * - thomas 1999-01-09
+    * Back to requiring a 4 digit year. We accept a two digit
+    * year farther down. - thomas 2000-03-28
+    ***/
+   else if (flen >= 4)
+   {
+       *tmask = DTK_M(YEAR);
+
+       /* already have a year? then see if we can substitute... */
+       if ((fmask & DTK_M(YEAR)) && (!(fmask & DTK_M(DAY)))
+           && ((tm->tm_year >= 1) && (tm->tm_year <= 31)))
+       {
+           tm->tm_mday = tm->tm_year;
+           *tmask = DTK_M(DAY);
+       }
+
+       tm->tm_year = val;
+   }
+
+   /* already have year? then could be month */
+   else if ((fmask & DTK_M(YEAR)) && (!(fmask & DTK_M(MONTH)))
+            && ((val >= 1) && (val <= 12)))
+   {
+       *tmask = DTK_M(MONTH);
+       tm->tm_mon = val;
+   }
+   /* no year and EuroDates enabled? then could be day */
+   else if ((EuroDates || (fmask & DTK_M(MONTH)))
+            && (!(fmask & DTK_M(YEAR)) && !(fmask & DTK_M(DAY)))
+            && ((val >= 1) && (val <= 31)))
+   {
+       *tmask = DTK_M(DAY);
+       tm->tm_mday = val;
+   }
+   else if ((!(fmask & DTK_M(MONTH)))
+            && ((val >= 1) && (val <= 12)))
+   {
+       *tmask = DTK_M(MONTH);
+       tm->tm_mon = val;
+   }
+   else if ((!(fmask & DTK_M(DAY)))
+            && ((val >= 1) && (val <= 31)))
+   {
+       *tmask = DTK_M(DAY);
+       tm->tm_mday = val;
+   }
+
+   /*
+    * Check for 2 or 4 or more digits, but currently we reach here only
+    * if two digits. - thomas 2000-03-28
+    */
+   else if (!(fmask & DTK_M(YEAR))
+            && ((flen >= 4) || (flen == 2)))
+   {
+       *tmask = DTK_M(YEAR);
+       tm->tm_year = val;
+
+       /* adjust ONLY if exactly two digits... */
+       *is2digits = (flen == 2);
+   }
+   else
+       return -1;
+
+   return 0;
+}  /* DecodeNumber() */
+
+/* DecodeDate()
+ * Decode date string which includes delimiters.
+ * Insist on a complete set of fields.
+ */
+static int
+DecodeDate(char *str, int fmask, int *tmask, struct tm * tm, bool EuroDates)
+{
+   fsec_t      fsec;
+
+   int         nf = 0;
+   int         i,
+               len;
+   int         bc = FALSE;
+   int         is2digits = FALSE;
+   int         type,
+               val,
+               dmask = 0;
+   char       *field[MAXDATEFIELDS];
+
+   /* parse this string... */
+   while ((*str != '\0') && (nf < MAXDATEFIELDS))
+   {
+       /* skip field separators */
+       while (!isalnum((unsigned char) *str))
+           str++;
+
+       field[nf] = str;
+       if (isdigit((unsigned char) *str))
+       {
+           while (isdigit((unsigned char) *str))
+               str++;
+       }
+       else if (isalpha((unsigned char) *str))
+       {
+           while (isalpha((unsigned char) *str))
+               str++;
+       }
+
+       /* Just get rid of any non-digit, non-alpha characters... */
+       if (*str != '\0')
+           *str++ = '\0';
+       nf++;
+   }
+
+#if 0
+   /* don't allow too many fields */
+   if (nf > 3)
+       return -1;
+#endif
+
+   *tmask = 0;
+
+   /* look first for text fields, since that will be unambiguous month */
+   for (i = 0; i < nf; i++)
+   {
+       if (isalpha((unsigned char) *field[i]))
+       {
+           type = DecodeSpecial(i, field[i], &val);
+           if (type == IGNORE_DTF)
+               continue;
+
+           dmask = DTK_M(type);
+           switch (type)
+           {
+               case MONTH:
+                   tm->tm_mon = val;
+                   break;
+
+               case ADBC:
+                   bc = (val == BC);
+                   break;
+
+               default:
+                   return -1;
+           }
+           if (fmask & dmask)
+               return -1;
+
+           fmask |= dmask;
+           *tmask |= dmask;
+
+           /* mark this field as being completed */
+           field[i] = NULL;
+       }
+   }
+
+   /* now pick up remaining numeric fields */
+   for (i = 0; i < nf; i++)
+   {
+       if (field[i] == NULL)
+           continue;
+
+       if ((len = strlen(field[i])) <= 0)
+           return -1;
+
+       if (DecodeNumber(len, field[i], fmask, &dmask, tm, &fsec, &is2digits, EuroDates) != 0)
+           return -1;
+
+       if (fmask & dmask)
+           return -1;
+
+       fmask |= dmask;
+       *tmask |= dmask;
+   }
+
+   if ((fmask & ~(DTK_M(DOY) | DTK_M(TZ))) != DTK_DATE_M)
+       return -1;
+
+   /* there is no year zero in AD/BC notation; i.e. "1 BC" == year 0 */
+   if (bc)
+   {
+       if (tm->tm_year > 0)
+           tm->tm_year = -(tm->tm_year - 1);
+       else
+           return -1;
+   }
+   else if (is2digits)
+   {
+       if (tm->tm_year < 70)
+           tm->tm_year += 2000;
+       else if (tm->tm_year < 100)
+           tm->tm_year += 1900;
+   }
+
+   return 0;
+}  /* DecodeDate() */
+
+
+/* DecodeTime()
+ * Decode time string which includes delimiters.
+ * Only check the lower limit on hours, since this same code
+ * can be used to represent time spans.
+ */
+static int
+DecodeTime(char *str, int fmask, int *tmask, struct tm * tm, fsec_t *fsec)
+{
+   char       *cp;
+
+   *tmask = DTK_TIME_M;
+
+   tm->tm_hour = strtol(str, &cp, 10);
+   if (*cp != ':')
+       return -1;
+   str = cp + 1;
+   tm->tm_min = strtol(str, &cp, 10);
+   if (*cp == '\0')
+   {
+       tm->tm_sec = 0;
+       *fsec = 0;
+   }
+   else if (*cp != ':')
+       return -1;
+   else
+   {
+       str = cp + 1;
+       tm->tm_sec = strtol(str, &cp, 10);
+       if (*cp == '\0')
+           *fsec = 0;
+       else if (*cp == '.')
+       {
+#ifdef HAVE_INT64_TIMESTAMP
+           char        fstr[MAXDATELEN + 1];
+
+           /*
+            * OK, we have at most six digits to work with. Let's
+            * construct a string and then do the conversion to an
+            * integer.
+            */
+           strncpy(fstr, (cp + 1), 7);
+           strcpy((fstr + strlen(fstr)), "000000");
+           *(fstr + 6) = '\0';
+           *fsec = strtol(fstr, &cp, 10);
+#else
+           str = cp;
+           *fsec = strtod(str, &cp);
+#endif
+           if (*cp != '\0')
+               return -1;
+       }
+       else
+           return -1;
+   }
+
+   /* do a sanity check */
+#ifdef HAVE_INT64_TIMESTAMP
+   if ((tm->tm_hour < 0)
+       || (tm->tm_min < 0) || (tm->tm_min > 59)
+       || (tm->tm_sec < 0) || (tm->tm_sec > 59)
+       || (*fsec >= INT64CONST(1000000)))
+       return -1;
+#else
+   if ((tm->tm_hour < 0)
+       || (tm->tm_min < 0) || (tm->tm_min > 59)
+       || (tm->tm_sec < 0) || (tm->tm_sec > 59)
+       || (*fsec >= 1))
+       return -1;
+#endif
+
+   return 0;
+}  /* DecodeTime() */
+
+/* DecodeTimezone()
+ * Interpret string as a numeric timezone.
+ *
+ * Note: we allow timezone offsets up to 13:59.  There are places that
+ * use +1300 summer time.
+ */
+static int
+DecodeTimezone(char *str, int *tzp)
+{
+   int         tz;
+   int         hr,
+               min;
+   char       *cp;
+   int         len;
+
+   /* assume leading character is "+" or "-" */
+   hr = strtol((str + 1), &cp, 10);
+
+   /* explicit delimiter? */
+   if (*cp == ':')
+       min = strtol((cp + 1), &cp, 10);
+   /* otherwise, might have run things together... */
+   else if ((*cp == '\0') && ((len = strlen(str)) > 3))
+   {
+       min = strtol((str + len - 2), &cp, 10);
+       if ((min < 0) || (min >= 60))
+           return -1;
+
+       *(str + len - 2) = '\0';
+       hr = strtol((str + 1), &cp, 10);
+       if ((hr < 0) || (hr > 13))
+           return -1;
+   }
+   else
+       min = 0;
+
+   tz = (hr * 60 + min) * 60;
+   if (*str == '-')
+       tz = -tz;
+
+   *tzp = -tz;
+   return *cp != '\0';
+}  /* DecodeTimezone() */
+
+
+/* DecodePosixTimezone()
+ * Interpret string as a POSIX-compatible timezone:
+ * PST-hh:mm
+ * PST+h
+ * - thomas 2000-03-15
+ */
+static int
+DecodePosixTimezone(char *str, int *tzp)
+{
+   int         val,
+               tz;
+   int         type;
+   char       *cp;
+   char        delim;
+
+   cp = str;
+   while ((*cp != '\0') && isalpha((unsigned char) *cp))
+       cp++;
+
+   if (DecodeTimezone(cp, &tz) != 0)
+       return -1;
+
+   delim = *cp;
+   *cp = '\0';
+   type = DecodeSpecial(MAXDATEFIELDS - 1, str, &val);
+   *cp = delim;
+
+   switch (type)
+   {
+       case DTZ:
+       case TZ:
+           *tzp = (val * 60) - tz;
+           break;
+
+       default:
+           return -1;
+   }
+
+   return 0;
+}  /* DecodePosixTimezone() */
+
+/* ParseDateTime()
+ * Break string into tokens based on a date/time context.
+ * Several field types are assigned:
+ * DTK_NUMBER - digits and (possibly) a decimal point
+ * DTK_DATE - digits and two delimiters, or digits and text
+ * DTK_TIME - digits, colon delimiters, and possibly a decimal point
+ * DTK_STRING - text (no digits)
+ * DTK_SPECIAL - leading "+" or "-" followed by text
+ * DTK_TZ - leading "+" or "-" followed by digits
+ * Note that some field types can hold unexpected items:
+ * DTK_NUMBER can hold date fields (yy.ddd)
+ * DTK_STRING can hold months (January) and time zones (PST)
+ * DTK_DATE can hold Posix time zones (GMT-8)
+ */
+int
+ParseDateTime(char *timestr, char *lowstr,
+             char **field, int *ftype, int maxfields, int *numfields, char **endstr)
+{
+   int         nf = 0;
+   char       *lp = lowstr;
+
+   *endstr = timestr;
+   /* outer loop through fields */
+   while (*(*endstr) != '\0')
+   {
+       field[nf] = lp;
+
+       /* leading digit? then date or time */
+       if (isdigit((unsigned char) *(*endstr)))
+       {
+           *lp++ = *(*endstr)++;
+           while (isdigit((unsigned char) *(*endstr)))
+               *lp++ = *(*endstr)++;
+
+           /* time field? */
+           if (*(*endstr) == ':')
+           {
+               ftype[nf] = DTK_TIME;
+               *lp++ = *(*endstr)++;
+               while (isdigit((unsigned char) *(*endstr)) ||
+                      (*(*endstr) == ':') || (*(*endstr) == '.'))
+                   *lp++ = *(*endstr)++;
+           }
+           /* date field? allow embedded text month */
+           else if ((*(*endstr) == '-') || (*(*endstr) == '/') || (*(*endstr) == '.'))
+           {
+               /* save delimiting character to use later */
+               char       *dp = (*endstr);
+
+               *lp++ = *(*endstr)++;
+               /* second field is all digits? then no embedded text month */
+               if (isdigit((unsigned char) *(*endstr)))
+               {
+                   ftype[nf] = ((*dp == '.') ? DTK_NUMBER : DTK_DATE);
+                   while (isdigit((unsigned char) *(*endstr)))
+                       *lp++ = *(*endstr)++;
+
+                   /*
+                    * insist that the delimiters match to get a
+                    * three-field date.
+                    */
+                   if (*(*endstr) == *dp)
+                   {
+                       ftype[nf] = DTK_DATE;
+                       *lp++ = *(*endstr)++;
+                       while (isdigit((unsigned char) *(*endstr)) || (*(*endstr) == *dp))
+                           *lp++ = *(*endstr)++;
+                   }
+               }
+               else
+               {
+                   ftype[nf] = DTK_DATE;
+                   while (isalnum((unsigned char) *(*endstr)) || (*(*endstr) == *dp))
+                       *lp++ = tolower((unsigned char) *(*endstr)++);
+               }
+           }
+
+           /*
+            * otherwise, number only and will determine year, month, day,
+            * or concatenated fields later...
+            */
+           else
+               ftype[nf] = DTK_NUMBER;
+       }
+       /* Leading decimal point? Then fractional seconds... */
+       else if (*(*endstr) == '.')
+       {
+           *lp++ = *(*endstr)++;
+           while (isdigit((unsigned char) *(*endstr)))
+               *lp++ = *(*endstr)++;
+
+           ftype[nf] = DTK_NUMBER;
+       }
+
+       /*
+        * text? then date string, month, day of week, special, or
+        * timezone
+        */
+       else if (isalpha((unsigned char) *(*endstr)))
+       {
+           ftype[nf] = DTK_STRING;
+           *lp++ = tolower((unsigned char) *(*endstr)++);
+           while (isalpha((unsigned char) *(*endstr)))
+               *lp++ = tolower((unsigned char) *(*endstr)++);
+
+           /*
+            * Full date string with leading text month? Could also be a
+            * POSIX time zone...
+            */
+           if ((*(*endstr) == '-') || (*(*endstr) == '/') || (*(*endstr) == '.'))
+           {
+               char       *dp = (*endstr);
+
+               ftype[nf] = DTK_DATE;
+               *lp++ = *(*endstr)++;
+               while (isdigit((unsigned char) *(*endstr)) || (*(*endstr) == *dp))
+                   *lp++ = *(*endstr)++;
+           }
+       }
+       /* skip leading spaces */
+       else if (isspace((unsigned char) *(*endstr)))
+       {
+           (*endstr)++;
+           continue;
+       }
+       /* sign? then special or numeric timezone */
+       else if ((*(*endstr) == '+') || (*(*endstr) == '-'))
+       {
+           *lp++ = *(*endstr)++;
+           /* soak up leading whitespace */
+           while (isspace((unsigned char) *(*endstr)))
+               (*endstr)++;
+           /* numeric timezone? */
+           if (isdigit((unsigned char) *(*endstr)))
+           {
+               ftype[nf] = DTK_TZ;
+               *lp++ = *(*endstr)++;
+               while (isdigit((unsigned char) *(*endstr)) ||
+                      (*(*endstr) == ':') || (*(*endstr) == '.'))
+                   *lp++ = *(*endstr)++;
+           }
+           /* special? */
+           else if (isalpha((unsigned char) *(*endstr)))
+           {
+               ftype[nf] = DTK_SPECIAL;
+               *lp++ = tolower((unsigned char) *(*endstr)++);
+               while (isalpha((unsigned char) *(*endstr)))
+                   *lp++ = tolower((unsigned char) *(*endstr)++);
+           }
+           /* otherwise something wrong... */
+           else
+               return -1;
+       }
+       /* ignore punctuation but use as delimiter */
+       else if (ispunct((unsigned char) *(*endstr)))
+       {
+           (*endstr)++;
+           continue;
+
+       }
+       /* otherwise, something is not right... */
+       else
+           return -1;
+
+       /* force in a delimiter after each field */
+       *lp++ = '\0';
+       nf++;
+       if (nf > MAXDATEFIELDS)
+           return -1;
+   }
+
+   *numfields = nf;
+
+   return 0;
+}  /* ParseDateTime() */
+
+
+/* DecodeDateTime()
+ * Interpret previously parsed fields for general date and time.
+ * Return 0 if full date, 1 if only time, and -1 if problems.
+ *     External format(s):
+ *             " -- ::"
+ *             "Fri Feb-7-1997 15:23:27"
+ *             "Feb-7-1997 15:23:27"
+ *             "2-7-1997 15:23:27"
+ *             "1997-2-7 15:23:27"
+ *             "1997.038 15:23:27"     (day of year 1-366)
+ *     Also supports input in compact time:
+ *             "970207 152327"
+ *             "97038 152327"
+ *             "20011225T040506.789-07"
+ *
+ * Use the system-provided functions to get the current time zone
+ * if not specified in the input string.
+ * If the date is outside the time_t system-supported time range,
+ * then assume UTC time zone. - thomas 1997-05-27
+ */
+int
+DecodeDateTime(char **field, int *ftype, int nf,
+              int *dtype, struct tm * tm, fsec_t *fsec, int *tzp, bool EuroDates)
+{
+   int         fmask = 0,
+               tmask,
+               type;
+   int         ptype = 0;      /* "prefix type" for ISO y2001m02d04
+                                * format */
+   int         i;
+   int         val;
+   int         mer = HR24;
+   int         haveTextMonth = FALSE;
+   int         is2digits = FALSE;
+   int         bc = FALSE;
+
+   /***
+    * We'll insist on at least all of the date fields, but initialize the
+    * remaining fields in case they are not set later...
+    ***/
+   *dtype = DTK_DATE;
+   tm->tm_hour = 0;
+   tm->tm_min = 0;
+   tm->tm_sec = 0;
+   *fsec = 0;
+   /* don't know daylight savings time status apriori */
+   tm->tm_isdst = -1;
+   if (tzp != NULL)
+       *tzp = 0;
+
+   for (i = 0; i < nf; i++)
+   {
+       switch (ftype[i])
+       {
+           case DTK_DATE:
+               /***
+                * Integral julian day with attached time zone?
+                * All other forms with JD will be separated into
+                * distinct fields, so we handle just this case here.
+                ***/
+               if (ptype == DTK_JULIAN)
+               {
+                   char       *cp;
+                   int         val;
+
+                   if (tzp == NULL)
+                       return -1;
+
+                   val = strtol(field[i], &cp, 10);
+                   if (*cp != '-')
+                       return -1;
+
+                   j2date(val, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
+                   /* Get the time zone from the end of the string */
+                   if (DecodeTimezone(cp, tzp) != 0)
+                       return -1;
+
+                   tmask = DTK_DATE_M | DTK_TIME_M | DTK_M(TZ);
+                   ptype = 0;
+                   break;
+               }
+               /***
+                * Already have a date? Then this might be a POSIX time
+                * zone with an embedded dash (e.g. "PST-3" == "EST") or
+                * a run-together time with trailing time zone (e.g. hhmmss-zz).
+                * - thomas 2001-12-25
+                ***/
+               else if (((fmask & DTK_DATE_M) == DTK_DATE_M)
+                        || (ptype != 0))
+               {
+                   /* No time zone accepted? Then quit... */
+                   if (tzp == NULL)
+                       return -1;
+
+                   if (isdigit((unsigned char) *field[i]) || ptype != 0)
+                   {
+                       char       *cp;
+
+                       if (ptype != 0)
+                       {
+                           /* Sanity check; should not fail this test */
+                           if (ptype != DTK_TIME)
+                               return -1;
+                           ptype = 0;
+                       }
+
+                       /*
+                        * Starts with a digit but we already have a time
+                        * field? Then we are in trouble with a date and
+                        * time already...
+                        */
+                       if ((fmask & DTK_TIME_M) == DTK_TIME_M)
+                           return -1;
+
+                       if ((cp = strchr(field[i], '-')) == NULL)
+                           return -1;
+
+                       /* Get the time zone from the end of the string */
+                       if (DecodeTimezone(cp, tzp) != 0)
+                           return -1;
+                       *cp = '\0';
+
+                       /*
+                        * Then read the rest of the field as a
+                        * concatenated time
+                        */
+                       if ((ftype[i] = DecodeNumberField(strlen(field[i]), field[i], fmask,
+                                     &tmask, tm, fsec, &is2digits, EuroDates)) < 0)
+                           return -1;
+
+                       /*
+                        * modify tmask after returning from
+                        * DecodeNumberField()
+                        */
+                       tmask |= DTK_M(TZ);
+                   }
+                   else
+                   {
+                       if (DecodePosixTimezone(field[i], tzp) != 0)
+                           return -1;
+
+                       ftype[i] = DTK_TZ;
+                       tmask = DTK_M(TZ);
+                   }
+               }
+               else if (DecodeDate(field[i], fmask, &tmask, tm, EuroDates) != 0)
+                   return -1;
+               break;
+
+           case DTK_TIME:
+               if (DecodeTime(field[i], fmask, &tmask, tm, fsec) != 0)
+                   return -1;
+
+               /*
+                * Check upper limit on hours; other limits checked in
+                * DecodeTime()
+                */
+               if (tm->tm_hour > 23)
+                   return -1;
+               break;
+
+           case DTK_TZ:
+               {
+                   int         tz;
+
+                   if (tzp == NULL)
+                       return -1;
+
+                   if (DecodeTimezone(field[i], &tz) != 0)
+                       return -1;
+
+                   /*
+                    * Already have a time zone? Then maybe this is the
+                    * second field of a POSIX time: EST+3 (equivalent to
+                    * PST)
+                    */
+                   if ((i > 0) && ((fmask & DTK_M(TZ)) != 0)
+                       && (ftype[i - 1] == DTK_TZ)
+                       && (isalpha((unsigned char) *field[i - 1])))
+                   {
+                       *tzp -= tz;
+                       tmask = 0;
+                   }
+                   else
+                   {
+                       *tzp = tz;
+                       tmask = DTK_M(TZ);
+                   }
+               }
+               break;
+
+           case DTK_NUMBER:
+
+               /*
+                * Was this an "ISO date" with embedded field labels? An
+                * example is "y2001m02d04" - thomas 2001-02-04
+                */
+               if (ptype != 0)
+               {
+                   char       *cp;
+                   int         val;
+
+                   val = strtol(field[i], &cp, 10);
+
+                   /*
+                    * only a few kinds are allowed to have an embedded
+                    * decimal
+                    */
+                   if (*cp == '.')
+                       switch (ptype)
+                       {
+                           case DTK_JULIAN:
+                           case DTK_TIME:
+                           case DTK_SECOND:
+                               break;
+                           default:
+                               return 1;
+                               break;
+                       }
+                   else if (*cp != '\0')
+                       return -1;
+
+                   switch (ptype)
+                   {
+                       case DTK_YEAR:
+                           tm->tm_year = val;
+                           tmask = DTK_M(YEAR);
+                           break;
+
+                       case DTK_MONTH:
+
+                           /*
+                            * already have a month and hour? then assume
+                            * minutes
+                            */
+                           if (((fmask & DTK_M(MONTH)) != 0)
+                               && ((fmask & DTK_M(HOUR)) != 0))
+                           {
+                               tm->tm_min = val;
+                               tmask = DTK_M(MINUTE);
+                           }
+                           else
+                           {
+                               tm->tm_mon = val;
+                               tmask = DTK_M(MONTH);
+                           }
+                           break;
+
+                       case DTK_DAY:
+                           tm->tm_mday = val;
+                           tmask = DTK_M(DAY);
+                           break;
+
+                       case DTK_HOUR:
+                           tm->tm_hour = val;
+                           tmask = DTK_M(HOUR);
+                           break;
+
+                       case DTK_MINUTE:
+                           tm->tm_min = val;
+                           tmask = DTK_M(MINUTE);
+                           break;
+
+                       case DTK_SECOND:
+                           tm->tm_sec = val;
+                           tmask = DTK_M(SECOND);
+                           if (*cp == '.')
+                           {
+                               double      frac;
+
+                               frac = strtod(cp, &cp);
+                               if (*cp != '\0')
+                                   return -1;
+#ifdef HAVE_INT64_TIMESTAMP
+                               *fsec = frac * 1000000;
+#else
+                               *fsec = frac;
+#endif
+                           }
+                           break;
+
+                       case DTK_TZ:
+                           tmask = DTK_M(TZ);
+                           if (DecodeTimezone(field[i], tzp) != 0)
+                               return -1;
+                           break;
+
+                       case DTK_JULIAN:
+                           /***
+                            * previous field was a label for "julian date"?
+                            ***/
+                           tmask = DTK_DATE_M;
+                           j2date(val, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
+                           /* fractional Julian Day? */
+                           if (*cp == '.')
+                           {
+                               double      time;
+
+                               time = strtod(cp, &cp);
+                               if (*cp != '\0')
+                                   return -1;
+
+                               tmask |= DTK_TIME_M;
+#ifdef HAVE_INT64_TIMESTAMP
+                               dt2time((time * 86400000000), &tm->tm_hour, &tm->tm_min, &tm->tm_sec, fsec);
+#else
+                               dt2time((time * 86400), &tm->tm_hour, &tm->tm_min, &tm->tm_sec, fsec);
+#endif
+                           }
+                           break;
+
+                       case DTK_TIME:
+                           /* previous field was "t" for ISO time */
+                           if ((ftype[i] = DecodeNumberField(strlen(field[i]), field[i], (fmask | DTK_DATE_M),
+                                     &tmask, tm, fsec, &is2digits, EuroDates)) < 0)
+                               return -1;
+
+                           if (tmask != DTK_TIME_M)
+                               return -1;
+                           break;
+
+                       default:
+                           return -1;
+                           break;
+                   }
+
+                   ptype = 0;
+                   *dtype = DTK_DATE;
+               }
+               else
+               {
+                   char       *cp;
+                   int         flen;
+
+                   flen = strlen(field[i]);
+                   cp = strchr(field[i], '.');
+
+                   /* Embedded decimal and no date yet? */
+                   if ((cp != NULL) && !(fmask & DTK_DATE_M))
+                   {
+                       if (DecodeDate(field[i], fmask, &tmask, tm, EuroDates) != 0)
+                           return -1;
+                   }
+                   /* embedded decimal and several digits before? */
+                   else if ((cp != NULL) && ((flen - strlen(cp)) > 2))
+                   {
+                       /*
+                        * Interpret as a concatenated date or time Set
+                        * the type field to allow decoding other fields
+                        * later. Example: 20011223 or 040506
+                        */
+                       if ((ftype[i] = DecodeNumberField(flen, field[i], fmask,
+                                     &tmask, tm, fsec, &is2digits, EuroDates)) < 0)
+                           return -1;
+                   }
+                   else if (flen > 4)
+                   {
+                       if ((ftype[i] = DecodeNumberField(flen, field[i], fmask,
+                                     &tmask, tm, fsec, &is2digits, EuroDates)) < 0)
+                           return -1;
+                   }
+                   /* otherwise it is a single date/time field... */
+                   else if (DecodeNumber(flen, field[i], fmask,
+                                     &tmask, tm, fsec, &is2digits, EuroDates) != 0)
+                       return -1;
+               }
+               break;
+
+           case DTK_STRING:
+           case DTK_SPECIAL:
+               type = DecodeSpecial(i, field[i], &val);
+               if (type == IGNORE_DTF)
+                   continue;
+
+               tmask = DTK_M(type);
+               switch (type)
+               {
+                   case RESERV:
+                       switch (val)
+                       {
+                           case DTK_NOW:
+                               tmask = (DTK_DATE_M | DTK_TIME_M | DTK_M(TZ));
+                               *dtype = DTK_DATE;
+                               GetCurrentDateTime(tm);
+                               break;
+
+                           case DTK_YESTERDAY:
+                               tmask = DTK_DATE_M;
+                               *dtype = DTK_DATE;
+                               GetCurrentDateTime(tm);
+                               j2date((date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - 1),
+                               &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
+                               tm->tm_hour = 0;
+                               tm->tm_min = 0;
+                               tm->tm_sec = 0;
+                               break;
+
+                           case DTK_TODAY:
+                               tmask = DTK_DATE_M;
+                               *dtype = DTK_DATE;
+                               GetCurrentDateTime(tm);
+                               tm->tm_hour = 0;
+                               tm->tm_min = 0;
+                               tm->tm_sec = 0;
+                               break;
+
+                           case DTK_TOMORROW:
+                               tmask = DTK_DATE_M;
+                               *dtype = DTK_DATE;
+                               GetCurrentDateTime(tm);
+                               j2date((date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) + 1),
+                               &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
+                               tm->tm_hour = 0;
+                               tm->tm_min = 0;
+                               tm->tm_sec = 0;
+                               break;
+
+                           case DTK_ZULU:
+                               tmask = (DTK_TIME_M | DTK_M(TZ));
+                               *dtype = DTK_DATE;
+                               tm->tm_hour = 0;
+                               tm->tm_min = 0;
+                               tm->tm_sec = 0;
+                               if (tzp != NULL)
+                                   *tzp = 0;
+                               break;
+
+                           default:
+                               *dtype = val;
+                       }
+
+                       break;
+
+                   case MONTH:
+
+                       /*
+                        * already have a (numeric) month? then see if we
+                        * can substitute...
+                        */
+                       if ((fmask & DTK_M(MONTH)) && (!haveTextMonth)
+                           && (!(fmask & DTK_M(DAY)))
+                           && ((tm->tm_mon >= 1) && (tm->tm_mon <= 31)))
+                       {
+                           tm->tm_mday = tm->tm_mon;
+                           tmask = DTK_M(DAY);
+                       }
+                       haveTextMonth = TRUE;
+                       tm->tm_mon = val;
+                       break;
+
+                   case DTZMOD:
+
+                       /*
+                        * daylight savings time modifier (solves "MET
+                        * DST" syntax)
+                        */
+                       tmask |= DTK_M(DTZ);
+                       tm->tm_isdst = 1;
+                       if (tzp == NULL)
+                           return -1;
+                       *tzp += val * 60;
+                       break;
+
+                   case DTZ:
+
+                       /*
+                        * set mask for TZ here _or_ check for DTZ later
+                        * when getting default timezone
+                        */
+                       tmask |= DTK_M(TZ);
+                       tm->tm_isdst = 1;
+                       if (tzp == NULL)
+                           return -1;
+                       *tzp = val * 60;
+                       ftype[i] = DTK_TZ;
+                       break;
+
+                   case TZ:
+                       tm->tm_isdst = 0;
+                       if (tzp == NULL)
+                           return -1;
+                       *tzp = val * 60;
+                       ftype[i] = DTK_TZ;
+                       break;
+
+                   case IGNORE_DTF:
+                       break;
+
+                   case AMPM:
+                       mer = val;
+                       break;
+
+                   case ADBC:
+                       bc = (val == BC);
+                       break;
+
+                   case DOW:
+                       tm->tm_wday = val;
+                       break;
+
+                   case UNITS:
+                       tmask = 0;
+                       ptype = val;
+                       break;
+
+                   case ISOTIME:
+
+                       /*
+                        * This is a filler field "t" indicating that the
+                        * next field is time. Try to verify that this is
+                        * sensible.
+                        */
+                       tmask = 0;
+
+                       /* No preceeding date? Then quit... */
+                       if ((fmask & DTK_DATE_M) != DTK_DATE_M)
+                           return -1;
+
+                       /***
+                        * We will need one of the following fields:
+                        *  DTK_NUMBER should be hhmmss.fff
+                        *  DTK_TIME should be hh:mm:ss.fff
+                        *  DTK_DATE should be hhmmss-zz
+                        ***/
+                       if ((i >= (nf - 1))
+                           || ((ftype[i + 1] != DTK_NUMBER)
+                               && (ftype[i + 1] != DTK_TIME)
+                               && (ftype[i + 1] != DTK_DATE)))
+                           return -1;
+
+                       ptype = val;
+                       break;
+
+                   default:
+                       return -1;
+               }
+               break;
+
+           default:
+               return -1;
+       }
+
+       if (tmask & fmask)
+           return -1;
+       fmask |= tmask;
+   }
+
+   /* there is no year zero in AD/BC notation; i.e. "1 BC" == year 0 */
+   if (bc)
+   {
+       if (tm->tm_year > 0)
+           tm->tm_year = -(tm->tm_year - 1);
+       else
+           return -1;
+   }
+   else if (is2digits)
+   {
+       if (tm->tm_year < 70)
+           tm->tm_year += 2000;
+       else if (tm->tm_year < 100)
+           tm->tm_year += 1900;
+   }
+
+   if ((mer != HR24) && (tm->tm_hour > 12))
+       return -1;
+   if ((mer == AM) && (tm->tm_hour == 12))
+       tm->tm_hour = 0;
+   else if ((mer == PM) && (tm->tm_hour != 12))
+       tm->tm_hour += 12;
+
+   /* do additional checking for full date specs... */
+   if (*dtype == DTK_DATE)
+   {
+       if ((fmask & DTK_DATE_M) != DTK_DATE_M)
+           return ((fmask & DTK_TIME_M) == DTK_TIME_M) ? 1 : -1;
+
+       /*
+        * check for valid day of month, now that we know for sure the
+        * month and year...
+        */
+       if ((tm->tm_mday < 1)
+        || (tm->tm_mday > day_tab[isleap(tm->tm_year)][tm->tm_mon - 1]))
+           return -1;
+
+       /* timezone not specified? then find local timezone if possible */
+       if (((fmask & DTK_DATE_M) == DTK_DATE_M)
+           && (tzp != NULL) && (!(fmask & DTK_M(TZ))))
+       {
+           /*
+            * daylight savings time modifier but no standard timezone?
+            * then error
+            */
+           if (fmask & DTK_M(DTZMOD))
+               return -1;
+
+           *tzp = DetermineLocalTimeZone(tm);
+       }
+   }
+
+   return 0;
+}  /* DecodeDateTime() */
+
diff --git a/src/interfaces/ecpg/pgtypeslib/extern.h b/src/interfaces/ecpg/pgtypeslib/extern.h
new file mode 100644 (file)
index 0000000..b843107
--- /dev/null
@@ -0,0 +1,4 @@
+#include 
+   
+extern char *pgtypes_alloc(long);
+extern char *pgtypes_strdup(char *);
index 03fa42089aea1aac371bd57f1ed734bee3744c01..d164579f5a5183f76941981c18e5a35894554c69 100644 (file)
@@ -5,9 +5,9 @@
 #include 
 #include 
 #include 
-#include 
 
 #include "c.h"
+#include "extern.h"
 #include "numeric.h"
 #include "pgtypes_error.h"
 
 
 #include "pgtypes_numeric.h"
 
-static char *
-pgtypes_alloc(long size)
-{
-   char *new = (char *) calloc(1L, size);
-
-   if (!new)
-   {
-       errno = ENOMEM;
-       return NULL;
-   }
-
-   memset(new, '\0', size);
-   return (new);
-}
-
 #if 0
 /* ----------
  * apply_typmod() -
diff --git a/src/interfaces/ecpg/pgtypeslib/timestamp.c b/src/interfaces/ecpg/pgtypeslib/timestamp.c
new file mode 100644 (file)
index 0000000..2bf3557
--- /dev/null
@@ -0,0 +1,356 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#ifdef __FAST_MATH__
+#error -ffast-math is known to break this code
+#endif
+
+#include "dt.h"
+#include "extern.h"
+#include "pgtypes_error.h"
+#include "pgtypes_timestamp.h"
+
+#ifdef HAVE_INT64_TIMESTAMP
+static int64
+time2t(const int hour, const int min, const int sec, const fsec_t fsec)
+{
+        return ((((((hour * 60) + min) * 60) + sec) * INT64CONST(1000000)) + fsec);
+}       /* time2t() */
+
+#else
+static double
+time2t(const int hour, const int min, const int sec, const fsec_t fsec)
+{
+        return ((((hour * 60) + min) * 60) + sec + fsec);
+}       /* time2t() */
+#endif
+
+static Timestamp
+dt2local(Timestamp dt, int tz)
+{
+#ifdef HAVE_INT64_TIMESTAMP
+        dt -= (tz * INT64CONST(1000000));
+#else
+        dt -= tz;
+        dt = JROUND(dt);
+#endif
+                       return dt;
+}       /* dt2local() */
+
+/* tm2timestamp()
+ * Convert a tm structure to a timestamp data type.
+ * Note that year is _not_ 1900-based, but is an explicit full value.
+ * Also, month is one-based, _not_ zero-based.
+ */
+static int
+tm2timestamp(struct tm * tm, fsec_t fsec, int *tzp, Timestamp *result)
+{
+#ifdef HAVE_INT64_TIMESTAMP
+   int         date;
+   int64       time;
+
+#else
+   double      date,
+               time;
+#endif
+
+   /* Julian day routines are not correct for negative Julian days */
+   if (!IS_VALID_JULIAN(tm->tm_year, tm->tm_mon, tm->tm_mday))
+       return -1;
+
+   date = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - date2j(2000, 1, 1);
+   time = time2t(tm->tm_hour, tm->tm_min, tm->tm_sec, fsec);
+#ifdef HAVE_INT64_TIMESTAMP
+   *result = ((date * INT64CONST(86400000000)) + time);
+   if ((*result < 0 && date >= 0) || (*result >= 0 && date < 0))
+       elog(ERROR, "TIMESTAMP out of range '%04d-%02d-%02d'",
+           tm->tm_year, tm->tm_mon, tm->tm_mday);
+#else
+   *result = ((date * 86400) + time);
+#endif
+   if (tzp != NULL)
+       *result = dt2local(*result, -(*tzp));
+
+   return 0;
+}  /* tm2timestamp() */
+
+static Timestamp
+SetEpochTimestamp(void)
+{
+        Timestamp       dt;
+        struct tm       tt, *tm = &tt;
+
+   GetEpochTime(tm);
+   tm2timestamp(tm, 0, NULL, &dt);
+        return dt;
+}       /* SetEpochTimestamp() */
+
+static void
+dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec)
+{
+#ifdef HAVE_INT64_TIMESTAMP
+        int64           time;
+#else
+        double          time;
+#endif
+
+        time = jd;
+
+#ifdef HAVE_INT64_TIMESTAMP
+        *hour = (time / INT64CONST(3600000000));
+        time -= ((*hour) * INT64CONST(3600000000));
+        *min = (time / INT64CONST(60000000));
+        time -= ((*min) * INT64CONST(60000000));
+        *sec = (time / INT64CONST(1000000));
+        *fsec = (time - (*sec * INT64CONST(1000000)));
+        *sec = (time / INT64CONST(1000000));
+        *fsec = (time - (*sec * INT64CONST(1000000)));
+#else
+        *hour = (time / 3600);
+        time -= ((*hour) * 3600);
+        *min = (time / 60);
+        time -= ((*min) * 60);
+        *sec = time;
+        *fsec = JROUND(time - *sec);
+#endif
+        return;
+}       /* dt2time() */
+
+/* timestamp2tm()
+ * Convert timestamp data type to POSIX time structure.
+ * Note that year is _not_ 1900-based, but is an explicit full value.
+ * Also, month is one-based, _not_ zero-based.
+ * Returns:
+ *  0 on success
+ * -1 on out of range
+ *
+ * For dates within the system-supported time_t range, convert to the
+ * local time zone. If out of this range, leave as GMT. - tgl 97/05/27
+ */
+static int
+timestamp2tm(Timestamp dt, int *tzp, struct tm * tm, fsec_t *fsec, char **tzn)
+{
+#ifdef HAVE_INT64_TIMESTAMP
+   int         date,
+               date0;
+   int64       time;
+
+#else
+   double      date,
+               date0;
+   double      time;
+#endif
+   time_t      utime;
+
+#if defined(HAVE_TM_ZONE) || defined(HAVE_INT_TIMEZONE)
+   struct tm  *tx;
+#endif
+
+   date0 = date2j(2000, 1, 1);
+
+   time = dt;
+#ifdef HAVE_INT64_TIMESTAMP
+   TMODULO(time, date, INT64CONST(86400000000));
+
+   if (time < INT64CONST(0))
+   {
+       time += INT64CONST(86400000000);
+       date -= 1;
+   }
+#else
+   TMODULO(time, date, 86400e0);
+
+   if (time < 0)
+   {
+       time += 86400;
+       date -= 1;
+   }
+#endif
+
+   /* Julian day routine does not work for negative Julian days */
+   if (date < -date0)
+       return -1;
+
+   /* add offset to go from J2000 back to standard Julian date */
+   date += date0;
+
+   j2date((int) date, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
+   dt2time(time, &tm->tm_hour, &tm->tm_min, &tm->tm_sec, fsec);
+
+   if (tzp != NULL)
+   {
+       /*
+        * Does this fall within the capabilities of the localtime()
+        * interface? Then use this to rotate to the local time zone.
+        */
+       if (IS_VALID_UTIME(tm->tm_year, tm->tm_mon, tm->tm_mday))
+       {
+#ifdef HAVE_INT64_TIMESTAMP
+           utime = ((dt / INT64CONST(1000000))
+                  + ((date0 - date2j(1970, 1, 1)) * INT64CONST(86400)));
+#else
+           utime = (dt + ((date0 - date2j(1970, 1, 1)) * 86400));
+#endif
+
+#if defined(HAVE_TM_ZONE) || defined(HAVE_INT_TIMEZONE)
+           tx = localtime(&utime);
+           tm->tm_year = tx->tm_year + 1900;
+           tm->tm_mon = tx->tm_mon + 1;
+           tm->tm_mday = tx->tm_mday;
+           tm->tm_hour = tx->tm_hour;
+           tm->tm_min = tx->tm_min;
+           tm->tm_isdst = tx->tm_isdst;
+
+#if defined(HAVE_TM_ZONE)
+           tm->tm_gmtoff = tx->tm_gmtoff;
+           tm->tm_zone = tx->tm_zone;
+
+           *tzp = -(tm->tm_gmtoff);    /* tm_gmtoff is Sun/DEC-ism */
+           if (tzn != NULL)
+               *tzn = (char *) tm->tm_zone;
+#elif defined(HAVE_INT_TIMEZONE)
+           *tzp = ((tm->tm_isdst > 0) ? (TIMEZONE_GLOBAL - 3600) : TIMEZONE_GLOBAL);
+           if (tzn != NULL)
+               *tzn = tzname[(tm->tm_isdst > 0)];
+#endif
+
+#else                          /* not (HAVE_TM_ZONE || HAVE_INT_TIMEZONE) */
+           *tzp = 0;
+           /* Mark this as *no* time zone available */
+           tm->tm_isdst = -1;
+           if (tzn != NULL)
+               *tzn = NULL;
+#endif
+
+           dt = dt2local(dt, *tzp);
+       }
+       else
+       {
+           *tzp = 0;
+           /* Mark this as *no* time zone available */
+           tm->tm_isdst = -1;
+           if (tzn != NULL)
+               *tzn = NULL;
+       }
+   }
+   else
+   {
+       tm->tm_isdst = -1;
+       if (tzn != NULL)
+           *tzn = NULL;
+   }
+
+   return 0;
+}  /* timestamp2tm() */
+
+/* EncodeSpecialTimestamp()
+ *  * Convert reserved timestamp data type to string.
+ *   */
+static int
+EncodeSpecialTimestamp(Timestamp dt, char *str)
+{
+        if (TIMESTAMP_IS_NOBEGIN(dt))
+                strcpy(str, EARLY);
+        else if (TIMESTAMP_IS_NOEND(dt))
+                strcpy(str, LATE);
+        else
+                return FALSE;
+
+        return TRUE;
+}       /* EncodeSpecialTimestamp() */
+
+Timestamp
+PGTYPEStimestamp_atot(char *str, char **endptr)
+{
+   Timestamp   result;
+#ifdef HAVE_INT64_TIMESTAMP
+   int64       noresult = 0;
+#else
+   double      noresult = 0.0;
+#endif
+   fsec_t      fsec;
+   struct tm   tt, *tm = &tt;
+   int         tz;
+   int         dtype;
+   int         nf;
+   char       *field[MAXDATEFIELDS];
+   int         ftype[MAXDATEFIELDS];
+   char        lowstr[MAXDATELEN + MAXDATEFIELDS];
+        char            *realptr;
+   char **ptr = (endptr != NULL) ? endptr : &realptr;
+
+   errno = 0;
+   if (strlen(str) >= sizeof(lowstr))
+   {
+       errno = PGTYPES_BAD_TIMESTAMP;
+       return (noresult);
+   }
+
+   if ((ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf, ptr) != 0)
+     || (DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz, 0) != 0))
+   {
+                errno = PGTYPES_BAD_TIMESTAMP;
+                return (noresult);
+        }
+   
+   switch (dtype)
+   {
+       case DTK_DATE:
+           if (tm2timestamp(tm, fsec, NULL, &result) != 0)
+           {
+               errno = PGTYPES_BAD_TIMESTAMP;
+               return (noresult);;
+           }
+           break;
+
+       case DTK_EPOCH:
+           result = SetEpochTimestamp();
+           break;
+
+       case DTK_LATE:
+           TIMESTAMP_NOEND(result);
+           break;
+
+       case DTK_EARLY:
+           TIMESTAMP_NOBEGIN(result);
+           break;
+
+       case DTK_INVALID:
+           errno = PGTYPES_BAD_TIMESTAMP;
+           return (noresult);
+
+       default:
+           errno = PGTYPES_BAD_TIMESTAMP;
+           return (noresult);
+   }
+
+   /* AdjustTimestampForTypmod(&result, typmod); */
+
+   return result;
+}
+
+char *
+PGTYPEStimestamp_ttoa(Timestamp tstamp)
+{
+        struct tm       tt, *tm = &tt;
+        char            buf[MAXDATELEN + 1];
+   char       *tzn = NULL;
+   fsec_t          fsec;
+   int     DateStyle = 0;
+
+   if (TIMESTAMP_NOT_FINITE(tstamp))
+                EncodeSpecialTimestamp(tstamp, buf);
+        else if (timestamp2tm(tstamp, NULL, tm, &fsec, NULL) == 0)
+                EncodeDateTime(tm, fsec, NULL, &tzn, DateStyle, buf, 0);
+        else
+   {
+       errno = PGTYPES_BAD_TIMESTAMP;
+       return NULL;
+   }
+        return pgtypes_strdup(buf);
+}
+
index 42c8799ea7795c426bccd4bf21c0ec7ab9829609..d618da6d8f9b3c7b9cef3fab4232f491b53fecad 100644 (file)
@@ -1,4 +1,4 @@
-/* $Header: /cvsroot/pgsql/src/interfaces/ecpg/preproc/Attic/preproc.y,v 1.212 2003/03/16 10:42:54 meskes Exp $ */
+/* $Header: /cvsroot/pgsql/src/interfaces/ecpg/preproc/Attic/preproc.y,v 1.213 2003/03/20 15:56:50 meskes Exp $ */
 
 /* Copyright comment */
 %{
@@ -4223,6 +4223,22 @@ single_vt_type: common_type
                $$.type_index = -1;
                $$.type_sizeof = NULL;
            }
+           else if (strcmp($1, "date") == 0)
+           {
+               $$.type_enum = ECPGt_date;
+               $$.type_str = make_str("Date");
+               $$.type_dimension = -1;
+               $$.type_index = -1;
+               $$.type_sizeof = NULL;
+           }
+           else if (strcmp($1, "timestamp") == 0)
+           {
+               $$.type_enum = ECPGt_timestamp;
+               $$.type_str = make_str("Timestamp");
+               $$.type_dimension = -1;
+               $$.type_index = -1;
+               $$.type_sizeof = NULL;
+           }
            else
            {
                /* this is for typedef'ed types */
@@ -4236,17 +4252,6 @@ single_vt_type: common_type
                struct_member_list[struct_level] = ECPGstruct_member_dup(this->struct_member_list);
            }
        }
-       | ECPGColLabelCommon '(' precision opt_scale ')'
-       {
-           if (strcmp($1, "numeric") != 0 && strcmp($1, "decimal") != 0)
-               mmerror(PARSE_ERROR, ET_ERROR, "Only numeric/decimal have precision/scale argument");
-           
-           $$.type_enum = ECPGt_numeric;
-           $$.type_str = EMPTY;
-           $$.type_dimension = -1;
-           $$.type_index = -1;
-           $$.type_sizeof = NULL;
-       }
        ;
 
 /*
@@ -4415,6 +4420,17 @@ common_type: simple_type
            $$.type_index = -1;
            $$.type_sizeof = NULL;
        }
+       | ECPGColLabelCommon '(' precision opt_scale ')'
+       {
+           if (strcmp($1, "numeric") != 0 && strcmp($1, "decimal") != 0)
+               mmerror(PARSE_ERROR, ET_ERROR, "Only numeric/decimal have precision/scale argument");
+           
+           $$.type_enum = ECPGt_numeric;
+           $$.type_str = EMPTY;
+           $$.type_dimension = -1;
+           $$.type_index = -1;
+           $$.type_sizeof = NULL;
+       }
        ;
 
 var_type:  common_type
@@ -4464,6 +4480,22 @@ var_type:    common_type
                $$.type_index = -1;
                $$.type_sizeof = NULL;
            }
+           else if (strcmp($1, "date") == 0)
+           {
+               $$.type_enum = ECPGt_date;
+               $$.type_str = make_str("Date");
+               $$.type_dimension = -1;
+               $$.type_index = -1;
+               $$.type_sizeof = NULL;
+           }
+           else if (strcmp($1, "timestamp") == 0)
+           {
+               $$.type_enum = ECPGt_timestamp;
+               $$.type_str = make_str("Timestamp");
+               $$.type_dimension = -1;
+               $$.type_index = -1;
+               $$.type_sizeof = NULL;
+           }
            else
            {
                /* this is for typedef'ed types */
@@ -4477,17 +4509,6 @@ var_type:    common_type
                struct_member_list[struct_level] = ECPGstruct_member_dup(this->struct_member_list);
            }
        }
-       | ECPGColLabelCommon '(' precision opt_scale ')'
-       {
-           if (strcmp($1, "numeric") != 0 && strcmp($1, "decimal") != 0)
-               mmerror(PARSE_ERROR, ET_ERROR, "Only numeric/decimal have precision/scale argument");
-           
-           $$.type_enum = ECPGt_numeric;
-           $$.type_str = EMPTY;
-           $$.type_dimension = -1;
-           $$.type_index = -1;
-           $$.type_sizeof = NULL;
-       }
        ;
 
 enum_type: SQL_ENUM opt_symbol enum_definition
index ec916a1e623d297af751f4eb563deafb0e477fcf..c55244fb1c4cfc279f007c7a367b5e0881ea5d18 100644 (file)
@@ -175,6 +175,12 @@ get_type(enum ECPGttype type)
        case ECPGt_descriptor:
            return ("ECPGt_descriptor");
            break;
+       case ECPGt_date:
+           return ("ECPGt_date");
+           break;
+       case ECPGt_timestamp:
+           return ("ECPGt_timestamp");
+           break;
        default:
            sprintf(errortext, "illegal variable type %d\n", type);
            yyerror(errortext);
@@ -330,6 +336,22 @@ ECPGdump_a_simple(FILE *o, const char *name, enum ECPGttype type,
                sprintf(variable, "&(%s%s)", prefix ? prefix : "", name);
                sprintf(offset, "sizeof(struct NumericVar)");
                break;
+           case ECPGt_date:
+
+               /*
+                *  we have to use a pointer and translate the variable type
+                */
+               sprintf(variable, "&(%s%s)", prefix ? prefix : "", name);
+               sprintf(offset, "sizeof(Date)");
+               break;
+           case ECPGt_timestamp:
+
+               /*
+                *  we have to use a pointer and translate the variable type
+                */
+               sprintf(variable, "&(%s%s)", prefix ? prefix : "", name);
+               sprintf(offset, "sizeof(Date)");
+               break;
            default:
 
                /*
index 7c9c0d1edc5cc582a03ff76992e6218ab34bbd64..c6424910ab5100749d57d4dbb887bfbf0e044bff 100644 (file)
@@ -1,4 +1,4 @@
-# $Header: /cvsroot/pgsql/src/interfaces/ecpg/test/Makefile,v 1.34 2003/03/16 10:42:54 meskes Exp $
+# $Header: /cvsroot/pgsql/src/interfaces/ecpg/test/Makefile,v 1.35 2003/03/20 15:56:50 meskes Exp $
 
 subdir = src/interfaces/ecpg/test
 top_builddir = ../../../..
@@ -8,7 +8,7 @@ override CPPFLAGS := -I$(srcdir)/../include $(CPPFLAGS) -g
 
 ECPG = ../preproc/ecpg -I$(srcdir)/../include
 
-TESTS = test1 test2 test3 test4 perftest dyntest dyntest2 test_notice test_code100 test_init testdynalloc num_test
+TESTS = test1 test2 test3 test4 perftest dyntest dyntest2 test_notice test_code100 test_init testdynalloc num_test dt_test
 
 all: $(TESTS)
 
diff --git a/src/interfaces/ecpg/test/dt_test.pgc b/src/interfaces/ecpg/test/dt_test.pgc
new file mode 100644 (file)
index 0000000..34f520f
--- /dev/null
@@ -0,0 +1,51 @@
+#include 
+#include 
+#include 
+
+int
+main()
+{
+   exec sql begin declare section;
+   date date1;
+   timestamp ts1;
+   char *text;
+   exec sql end declare section;
+#if 0
+   Date date2;
+   short int mdy[3] = { 4, 19, 1998 };
+#endif
+   FILE *dbgs;
+
+        if ((dbgs = fopen("log", "w")) != NULL)
+                 ECPGdebug(1, dbgs);
+        exec sql whenever sqlerror do sqlprint();
+        exec sql connect to mm;
+        exec sql create table date_test (d date, ts timestamp);
+
+   exec sql insert into date_test(d, ts) values ('Mon Jan 17 1966', '2000-7-12 17:34:29');
+
+   exec sql select * into :date1, :ts1 from date_test;
+   
+   text = PGTYPESdate_dtoa(date1);
+   printf ("Date: %s\n", text);
+   ts1 = PGTYPEStimestamp_atot("2000-7-12 17:34:29", NULL);
+   text = PGTYPEStimestamp_ttoa(ts1);
+   printf ("timestamp: %s\n", text);
+#if 0
+   PGTYPESdate_mdyjul(mdy, &date2);
+   printf("m: %d, d: %d, y: %d\n", mdy[0], mdy[1], mdy[2]);
+   /* reset */
+   mdy[0] = mdy[1] = mdy[2] = 0;
+
+   PGTYPESdate_julmdy(date2, mdy);
+   printf("m: %d, d: %d, y: %d\n", mdy[0], mdy[1], mdy[2]);
+#endif
+        exec sql rollback;
+        exec sql disconnect;
+
+   if (dbgs != NULL)
+       fclose(dbgs);
+                       
+   return (0);
+}
+
index f81b81b915cb345f0b81613bdce6ca70355df63f..f534df5da25458103f0d40772b763a22b6199f00 100644 (file)
@@ -8,6 +8,7 @@ main()
    NumericVar *value1, *value2, *res;
    exec sql begin declare section;
        decimal(14,7) des = {0, 0, 0, 0, 0, NULL, NULL} ;
+       numeric num;
    exec sql end declare section;
    double d;
    FILE *dbgs;
@@ -52,6 +53,13 @@ main()
    text = PGTYPESnumeric_ntoa(res);
    PGTYPESnumeric_ntod(res, &d);
    printf("div = %s %e\n", text, d);
+
+   exec sql rollback;
+   exec sql disconnect;
+
+   if (dbgs != NULL)
+                fclose(dbgs);
+       
    return (0);
 }