Started adding date and timestamp.
authorMichael Meskes <meskes@postgresql.org>
Thu, 20 Mar 2003 15:56:50 +0000 (15:56 +0000)
committerMichael Meskes <meskes@postgresql.org>
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 <errno.h>
+
+#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 <ctype.h>
+#include <errno.h>
+#include <time.h>
+#include <float.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#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 <ctype.h>
+#include <errno.h>
+#include <time.h>
+#include <float.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#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):
+ *                             "<weekday> <month>-<day>-<year> <hour>:<minute>:<second>"
+ *                             "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 <string.h>
+       
+extern char *pgtypes_alloc(long);
+extern char *pgtypes_strdup(char *);
index 03fa42089aea1aac371bd57f1ed734bee3744c01..d164579f5a5183f76941981c18e5a35894554c69 100644 (file)
@@ -5,9 +5,9 @@
 #include <math.h>
 #include <errno.h>
 #include <stdlib.h>
-#include <string.h>
 
 #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 <math.h>
+#include <time.h>
+#include <string.h>
+#include <errno.h>
+#include <float.h>
+#include <stdio.h>
+
+#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 <stdio.h>
+#include <pgtypes_date.h>
+#include <pgtypes_timestamp.h>
+
+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);
 }