diff options
Diffstat (limited to 'src')
22 files changed, 3674 insertions, 50 deletions
diff --git a/src/interfaces/ecpg/ChangeLog b/src/interfaces/ecpg/ChangeLog index e4c3548dfc4..bfe62f1fda5 100644 --- a/src/interfaces/ecpg/ChangeLog +++ b/src/interfaces/ecpg/ChangeLog @@ -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 diff --git a/src/interfaces/ecpg/ecpglib/data.c b/src/interfaces/ecpg/ecpglib/data.c index 85d5e30a1ba..81f9d62e691 100644 --- a/src/interfaces/ecpg/ecpglib/data.c +++ b/src/interfaces/ecpg/ecpglib/data.c @@ -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); diff --git a/src/interfaces/ecpg/ecpglib/execute.c b/src/interfaces/ecpg/ecpglib/execute.c index 8cb3fa36a67..0fdd925a09c 100644 --- a/src/interfaces/ecpg/ecpglib/execute.c +++ b/src/interfaces/ecpg/ecpglib/execute.c @@ -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)); diff --git a/src/interfaces/ecpg/ecpglib/typename.c b/src/interfaces/ecpg/ecpglib/typename.c index c970f767242..6ae846c3812 100644 --- a/src/interfaces/ecpg/ecpglib/typename.c +++ b/src/interfaces/ecpg/ecpglib/typename.c @@ -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(); } diff --git a/src/interfaces/ecpg/include/decimal.h b/src/interfaces/ecpg/include/decimal.h index f307207adce..2c13a33c708 100644 --- a/src/interfaces/ecpg/include/decimal.h +++ b/src/interfaces/ecpg/include/decimal.h @@ -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 */ diff --git a/src/interfaces/ecpg/include/ecpgtype.h b/src/interfaces/ecpg/include/ecpgtype.h index 6ed5f5d3e4f..31738d421f1 100644 --- a/src/interfaces/ecpg/include/ecpgtype.h +++ b/src/interfaces/ecpg/include/ecpgtype.h @@ -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 index 00000000000..882ddab82df --- /dev/null +++ b/src/interfaces/ecpg/include/pgtypes_date.h @@ -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 */ diff --git a/src/interfaces/ecpg/include/pgtypes_error.h b/src/interfaces/ecpg/include/pgtypes_error.h index e997b03ae01..7cab1971b98 100644 --- a/src/interfaces/ecpg/include/pgtypes_error.h +++ b/src/interfaces/ecpg/include/pgtypes_error.h @@ -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 index 00000000000..48a54b1381f --- /dev/null +++ b/src/interfaces/ecpg/include/pgtypes_timestamp.h @@ -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 */ diff --git a/src/interfaces/ecpg/pgtypeslib/Makefile b/src/interfaces/ecpg/pgtypeslib/Makefile index 73d174e3d23..ae691c7d21d 100644 --- a/src/interfaces/ecpg/pgtypeslib/Makefile +++ b/src/interfaces/ecpg/pgtypeslib/Makefile @@ -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 index 00000000000..b91bedcd581 --- /dev/null +++ b/src/interfaces/ecpg/pgtypeslib/common.c @@ -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 index 00000000000..46fd1ab96f9 --- /dev/null +++ b/src/interfaces/ecpg/pgtypeslib/datetime.c @@ -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 index 00000000000..d20c0cd819d --- /dev/null +++ b/src/interfaces/ecpg/pgtypeslib/dt.h @@ -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 index 00000000000..3b85f7df152 --- /dev/null +++ b/src/interfaces/ecpg/pgtypeslib/dt_common.c @@ -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 index 00000000000..b843107f653 --- /dev/null +++ b/src/interfaces/ecpg/pgtypeslib/extern.h @@ -0,0 +1,4 @@ +#include <string.h> + +extern char *pgtypes_alloc(long); +extern char *pgtypes_strdup(char *); diff --git a/src/interfaces/ecpg/pgtypeslib/numeric.c b/src/interfaces/ecpg/pgtypeslib/numeric.c index 03fa42089ae..d164579f5a5 100644 --- a/src/interfaces/ecpg/pgtypeslib/numeric.c +++ b/src/interfaces/ecpg/pgtypeslib/numeric.c @@ -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" @@ -25,21 +25,6 @@ #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 index 00000000000..2bf3557f760 --- /dev/null +++ b/src/interfaces/ecpg/pgtypeslib/timestamp.c @@ -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); +} + diff --git a/src/interfaces/ecpg/preproc/preproc.y b/src/interfaces/ecpg/preproc/preproc.y index 42c8799ea77..d618da6d8f9 100644 --- a/src/interfaces/ecpg/preproc/preproc.y +++ b/src/interfaces/ecpg/preproc/preproc.y @@ -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 diff --git a/src/interfaces/ecpg/preproc/type.c b/src/interfaces/ecpg/preproc/type.c index ec916a1e623..c55244fb1c4 100644 --- a/src/interfaces/ecpg/preproc/type.c +++ b/src/interfaces/ecpg/preproc/type.c @@ -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: /* diff --git a/src/interfaces/ecpg/test/Makefile b/src/interfaces/ecpg/test/Makefile index 7c9c0d1edc5..c6424910ab5 100644 --- a/src/interfaces/ecpg/test/Makefile +++ b/src/interfaces/ecpg/test/Makefile @@ -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 index 00000000000..34f520f891b --- /dev/null +++ b/src/interfaces/ecpg/test/dt_test.pgc @@ -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); +} + diff --git a/src/interfaces/ecpg/test/num_test.pgc b/src/interfaces/ecpg/test/num_test.pgc index f81b81b915c..f534df5da25 100644 --- a/src/interfaces/ecpg/test/num_test.pgc +++ b/src/interfaces/ecpg/test/num_test.pgc @@ -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); } |