Header And Logo

PostgreSQL
| The world's most advanced open source database.

interval.c

Go to the documentation of this file.
00001 /* src/interfaces/ecpg/pgtypeslib/interval.c */
00002 
00003 #include "postgres_fe.h"
00004 #include <time.h>
00005 #include <math.h>
00006 #include <limits.h>
00007 
00008 #ifdef __FAST_MATH__
00009 #error -ffast-math is known to break this code
00010 #endif
00011 
00012 #include "extern.h"
00013 #include "dt.h"
00014 #include "pgtypes_error.h"
00015 #include "pgtypes_interval.h"
00016 
00017 /* copy&pasted from .../src/backend/utils/adt/datetime.c */
00018 static int
00019 strtoi(const char *nptr, char **endptr, int base)
00020 {
00021     long        val;
00022 
00023     val = strtol(nptr, endptr, base);
00024 #ifdef HAVE_LONG_INT_64
00025     if (val != (long) ((int32) val))
00026         errno = ERANGE;
00027 #endif
00028     return (int) val;
00029 }
00030 
00031 /* copy&pasted from .../src/backend/utils/adt/datetime.c
00032  * and changesd struct pg_tm to struct tm
00033  */
00034 static void
00035 AdjustFractSeconds(double frac, struct /* pg_ */ tm * tm, fsec_t *fsec, int scale)
00036 {
00037     int         sec;
00038 
00039     if (frac == 0)
00040         return;
00041     frac *= scale;
00042     sec = (int) frac;
00043     tm->tm_sec += sec;
00044     frac -= sec;
00045 #ifdef HAVE_INT64_TIMESTAMP
00046     *fsec += rint(frac * 1000000);
00047 #else
00048     *fsec += frac;
00049 #endif
00050 }
00051 
00052 
00053 /* copy&pasted from .../src/backend/utils/adt/datetime.c
00054  * and changesd struct pg_tm to struct tm
00055  */
00056 static void
00057 AdjustFractDays(double frac, struct /* pg_ */ tm * tm, fsec_t *fsec, int scale)
00058 {
00059     int         extra_days;
00060 
00061     if (frac == 0)
00062         return;
00063     frac *= scale;
00064     extra_days = (int) frac;
00065     tm->tm_mday += extra_days;
00066     frac -= extra_days;
00067     AdjustFractSeconds(frac, tm, fsec, SECS_PER_DAY);
00068 }
00069 
00070 /* copy&pasted from .../src/backend/utils/adt/datetime.c */
00071 static int
00072 ParseISO8601Number(char *str, char **endptr, int *ipart, double *fpart)
00073 {
00074     double      val;
00075 
00076     if (!(isdigit((unsigned char) *str) || *str == '-' || *str == '.'))
00077         return DTERR_BAD_FORMAT;
00078     errno = 0;
00079     val = strtod(str, endptr);
00080     /* did we not see anything that looks like a double? */
00081     if (*endptr == str || errno != 0)
00082         return DTERR_BAD_FORMAT;
00083     /* watch out for overflow */
00084     if (val < INT_MIN || val > INT_MAX)
00085         return DTERR_FIELD_OVERFLOW;
00086     /* be very sure we truncate towards zero (cf dtrunc()) */
00087     if (val >= 0)
00088         *ipart = (int) floor(val);
00089     else
00090         *ipart = (int) -floor(-val);
00091     *fpart = val - *ipart;
00092     return 0;
00093 }
00094 
00095 /* copy&pasted from .../src/backend/utils/adt/datetime.c */
00096 static int
00097 ISO8601IntegerWidth(char *fieldstart)
00098 {
00099     /* We might have had a leading '-' */
00100     if (*fieldstart == '-')
00101         fieldstart++;
00102     return strspn(fieldstart, "0123456789");
00103 }
00104 
00105 
00106 /* copy&pasted from .../src/backend/utils/adt/datetime.c
00107  * and changesd struct pg_tm to struct tm
00108  */
00109 static inline void
00110 ClearPgTm(struct /* pg_ */ tm * tm, fsec_t *fsec)
00111 {
00112     tm->tm_year = 0;
00113     tm->tm_mon = 0;
00114     tm->tm_mday = 0;
00115     tm->tm_hour = 0;
00116     tm->tm_min = 0;
00117     tm->tm_sec = 0;
00118     *fsec = 0;
00119 }
00120 
00121 /* copy&pasted from .../src/backend/utils/adt/datetime.c
00122  *
00123  * * changesd struct pg_tm to struct tm
00124  *
00125  * * Made the function static
00126  */
00127 static int
00128 DecodeISO8601Interval(char *str,
00129                       int *dtype, struct /* pg_ */ tm * tm, fsec_t *fsec)
00130 {
00131     bool        datepart = true;
00132     bool        havefield = false;
00133 
00134     *dtype = DTK_DELTA;
00135     ClearPgTm(tm, fsec);
00136 
00137     if (strlen(str) < 2 || str[0] != 'P')
00138         return DTERR_BAD_FORMAT;
00139 
00140     str++;
00141     while (*str)
00142     {
00143         char       *fieldstart;
00144         int         val;
00145         double      fval;
00146         char        unit;
00147         int         dterr;
00148 
00149         if (*str == 'T')        /* T indicates the beginning of the time part */
00150         {
00151             datepart = false;
00152             havefield = false;
00153             str++;
00154             continue;
00155         }
00156 
00157         fieldstart = str;
00158         dterr = ParseISO8601Number(str, &str, &val, &fval);
00159         if (dterr)
00160             return dterr;
00161 
00162         /*
00163          * Note: we could step off the end of the string here.  Code below
00164          * *must* exit the loop if unit == '\0'.
00165          */
00166         unit = *str++;
00167 
00168         if (datepart)
00169         {
00170             switch (unit)       /* before T: Y M W D */
00171             {
00172                 case 'Y':
00173                     tm->tm_year += val;
00174                     tm->tm_mon += (fval * 12);
00175                     break;
00176                 case 'M':
00177                     tm->tm_mon += val;
00178                     AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
00179                     break;
00180                 case 'W':
00181                     tm->tm_mday += val * 7;
00182                     AdjustFractDays(fval, tm, fsec, 7);
00183                     break;
00184                 case 'D':
00185                     tm->tm_mday += val;
00186                     AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
00187                     break;
00188                 case 'T':       /* ISO 8601 4.4.3.3 Alternative Format / Basic */
00189                 case '\0':
00190                     if (ISO8601IntegerWidth(fieldstart) == 8 && !havefield)
00191                     {
00192                         tm->tm_year += val / 10000;
00193                         tm->tm_mon += (val / 100) % 100;
00194                         tm->tm_mday += val % 100;
00195                         AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
00196                         if (unit == '\0')
00197                             return 0;
00198                         datepart = false;
00199                         havefield = false;
00200                         continue;
00201                     }
00202                     /* Else fall through to extended alternative format */
00203                 case '-':       /* ISO 8601 4.4.3.3 Alternative Format,
00204                                  * Extended */
00205                     if (havefield)
00206                         return DTERR_BAD_FORMAT;
00207 
00208                     tm->tm_year += val;
00209                     tm->tm_mon += (fval * 12);
00210                     if (unit == '\0')
00211                         return 0;
00212                     if (unit == 'T')
00213                     {
00214                         datepart = false;
00215                         havefield = false;
00216                         continue;
00217                     }
00218 
00219                     dterr = ParseISO8601Number(str, &str, &val, &fval);
00220                     if (dterr)
00221                         return dterr;
00222                     tm->tm_mon += val;
00223                     AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
00224                     if (*str == '\0')
00225                         return 0;
00226                     if (*str == 'T')
00227                     {
00228                         datepart = false;
00229                         havefield = false;
00230                         continue;
00231                     }
00232                     if (*str != '-')
00233                         return DTERR_BAD_FORMAT;
00234                     str++;
00235 
00236                     dterr = ParseISO8601Number(str, &str, &val, &fval);
00237                     if (dterr)
00238                         return dterr;
00239                     tm->tm_mday += val;
00240                     AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
00241                     if (*str == '\0')
00242                         return 0;
00243                     if (*str == 'T')
00244                     {
00245                         datepart = false;
00246                         havefield = false;
00247                         continue;
00248                     }
00249                     return DTERR_BAD_FORMAT;
00250                 default:
00251                     /* not a valid date unit suffix */
00252                     return DTERR_BAD_FORMAT;
00253             }
00254         }
00255         else
00256         {
00257             switch (unit)       /* after T: H M S */
00258             {
00259                 case 'H':
00260                     tm->tm_hour += val;
00261                     AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
00262                     break;
00263                 case 'M':
00264                     tm->tm_min += val;
00265                     AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
00266                     break;
00267                 case 'S':
00268                     tm->tm_sec += val;
00269                     AdjustFractSeconds(fval, tm, fsec, 1);
00270                     break;
00271                 case '\0':      /* ISO 8601 4.4.3.3 Alternative Format */
00272                     if (ISO8601IntegerWidth(fieldstart) == 6 && !havefield)
00273                     {
00274                         tm->tm_hour += val / 10000;
00275                         tm->tm_min += (val / 100) % 100;
00276                         tm->tm_sec += val % 100;
00277                         AdjustFractSeconds(fval, tm, fsec, 1);
00278                         return 0;
00279                     }
00280                     /* Else fall through to extended alternative format */
00281                 case ':':       /* ISO 8601 4.4.3.3 Alternative Format,
00282                                  * Extended */
00283                     if (havefield)
00284                         return DTERR_BAD_FORMAT;
00285 
00286                     tm->tm_hour += val;
00287                     AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
00288                     if (unit == '\0')
00289                         return 0;
00290 
00291                     dterr = ParseISO8601Number(str, &str, &val, &fval);
00292                     if (dterr)
00293                         return dterr;
00294                     tm->tm_min += val;
00295                     AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
00296                     if (*str == '\0')
00297                         return 0;
00298                     if (*str != ':')
00299                         return DTERR_BAD_FORMAT;
00300                     str++;
00301 
00302                     dterr = ParseISO8601Number(str, &str, &val, &fval);
00303                     if (dterr)
00304                         return dterr;
00305                     tm->tm_sec += val;
00306                     AdjustFractSeconds(fval, tm, fsec, 1);
00307                     if (*str == '\0')
00308                         return 0;
00309                     return DTERR_BAD_FORMAT;
00310 
00311                 default:
00312                     /* not a valid time unit suffix */
00313                     return DTERR_BAD_FORMAT;
00314             }
00315         }
00316 
00317         havefield = true;
00318     }
00319 
00320     return 0;
00321 }
00322 
00323 
00324 
00325 /* copy&pasted from .../src/backend/utils/adt/datetime.c
00326  * with 3 exceptions
00327  *
00328  *  * changesd struct pg_tm to struct tm
00329  *
00330  *  * ECPG code called this without a 'range' parameter
00331  *    removed 'int range' from the argument list and
00332  *    places where DecodeTime is called; and added
00333  *       int range = INTERVAL_FULL_RANGE;
00334  *
00335  *  * ECPG semes not to have a global IntervalStyle
00336  *    so added
00337  *      int IntervalStyle = INTSTYLE_POSTGRES;
00338  *
00339  *  * Assert wasn't available so removed it.
00340  */
00341 int
00342 DecodeInterval(char **field, int *ftype, int nf,        /* int range, */
00343                int *dtype, struct /* pg_ */ tm * tm, fsec_t *fsec)
00344 {
00345     int         IntervalStyle = INTSTYLE_POSTGRES_VERBOSE;
00346     int         range = INTERVAL_FULL_RANGE;
00347     bool        is_before = FALSE;
00348     char       *cp;
00349     int         fmask = 0,
00350                 tmask,
00351                 type;
00352     int         i;
00353     int         dterr;
00354     int         val;
00355     double      fval;
00356 
00357     *dtype = DTK_DELTA;
00358     type = IGNORE_DTF;
00359     ClearPgTm(tm, fsec);
00360 
00361     /* read through list backwards to pick up units before values */
00362     for (i = nf - 1; i >= 0; i--)
00363     {
00364         switch (ftype[i])
00365         {
00366             case DTK_TIME:
00367                 dterr = DecodeTime(field[i],    /* range, */
00368                                    &tmask, tm, fsec);
00369                 if (dterr)
00370                     return dterr;
00371                 type = DTK_DAY;
00372                 break;
00373 
00374             case DTK_TZ:
00375 
00376                 /*
00377                  * Timezone is a token with a leading sign character and at
00378                  * least one digit; there could be ':', '.', '-' embedded in
00379                  * it as well.
00380                  */
00381                 /* Assert(*field[i] == '-' || *field[i] == '+'); */
00382 
00383                 /*
00384                  * Try for hh:mm or hh:mm:ss.  If not, fall through to
00385                  * DTK_NUMBER case, which can handle signed float numbers and
00386                  * signed year-month values.
00387                  */
00388                 if (strchr(field[i] + 1, ':') != NULL &&
00389                     DecodeTime(field[i] + 1,    /* INTERVAL_FULL_RANGE, */
00390                                &tmask, tm, fsec) == 0)
00391                 {
00392                     if (*field[i] == '-')
00393                     {
00394                         /* flip the sign on all fields */
00395                         tm->tm_hour = -tm->tm_hour;
00396                         tm->tm_min = -tm->tm_min;
00397                         tm->tm_sec = -tm->tm_sec;
00398                         *fsec = -(*fsec);
00399                     }
00400 
00401                     /*
00402                      * Set the next type to be a day, if units are not
00403                      * specified. This handles the case of '1 +02:03' since we
00404                      * are reading right to left.
00405                      */
00406                     type = DTK_DAY;
00407                     tmask = DTK_M(TZ);
00408                     break;
00409                 }
00410                 /* FALL THROUGH */
00411 
00412             case DTK_DATE:
00413             case DTK_NUMBER:
00414                 if (type == IGNORE_DTF)
00415                 {
00416                     /* use typmod to decide what rightmost field is */
00417                     switch (range)
00418                     {
00419                         case INTERVAL_MASK(YEAR):
00420                             type = DTK_YEAR;
00421                             break;
00422                         case INTERVAL_MASK(MONTH):
00423                         case INTERVAL_MASK(YEAR) | INTERVAL_MASK(MONTH):
00424                             type = DTK_MONTH;
00425                             break;
00426                         case INTERVAL_MASK(DAY):
00427                             type = DTK_DAY;
00428                             break;
00429                         case INTERVAL_MASK(HOUR):
00430                         case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR):
00431                         case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE):
00432                         case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
00433                             type = DTK_HOUR;
00434                             break;
00435                         case INTERVAL_MASK(MINUTE):
00436                         case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE):
00437                             type = DTK_MINUTE;
00438                             break;
00439                         case INTERVAL_MASK(SECOND):
00440                         case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
00441                         case INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
00442                             type = DTK_SECOND;
00443                             break;
00444                         default:
00445                             type = DTK_SECOND;
00446                             break;
00447                     }
00448                 }
00449 
00450                 errno = 0;
00451                 val = strtoi(field[i], &cp, 10);
00452                 if (errno == ERANGE)
00453                     return DTERR_FIELD_OVERFLOW;
00454 
00455                 if (*cp == '-')
00456                 {
00457                     /* SQL "years-months" syntax */
00458                     int         val2;
00459 
00460                     val2 = strtoi(cp + 1, &cp, 10);
00461                     if (errno == ERANGE || val2 < 0 || val2 >= MONTHS_PER_YEAR)
00462                         return DTERR_FIELD_OVERFLOW;
00463                     if (*cp != '\0')
00464                         return DTERR_BAD_FORMAT;
00465                     type = DTK_MONTH;
00466                     if (*field[i] == '-')
00467                         val2 = -val2;
00468                     val = val * MONTHS_PER_YEAR + val2;
00469                     fval = 0;
00470                 }
00471                 else if (*cp == '.')
00472                 {
00473                     errno = 0;
00474                     fval = strtod(cp, &cp);
00475                     if (*cp != '\0' || errno != 0)
00476                         return DTERR_BAD_FORMAT;
00477 
00478                     if (*field[i] == '-')
00479                         fval = -fval;
00480                 }
00481                 else if (*cp == '\0')
00482                     fval = 0;
00483                 else
00484                     return DTERR_BAD_FORMAT;
00485 
00486                 tmask = 0;      /* DTK_M(type); */
00487 
00488                 switch (type)
00489                 {
00490                     case DTK_MICROSEC:
00491 #ifdef HAVE_INT64_TIMESTAMP
00492                         *fsec += rint(val + fval);
00493 #else
00494                         *fsec += (val + fval) * 1e-6;
00495 #endif
00496                         tmask = DTK_M(MICROSECOND);
00497                         break;
00498 
00499                     case DTK_MILLISEC:
00500 #ifdef HAVE_INT64_TIMESTAMP
00501                         *fsec += rint((val + fval) * 1000);
00502 #else
00503                         *fsec += (val + fval) * 1e-3;
00504 #endif
00505                         tmask = DTK_M(MILLISECOND);
00506                         break;
00507 
00508                     case DTK_SECOND:
00509                         tm->tm_sec += val;
00510 #ifdef HAVE_INT64_TIMESTAMP
00511                         *fsec += rint(fval * 1000000);
00512 #else
00513                         *fsec += fval;
00514 #endif
00515 
00516                         /*
00517                          * If any subseconds were specified, consider this
00518                          * microsecond and millisecond input as well.
00519                          */
00520                         if (fval == 0)
00521                             tmask = DTK_M(SECOND);
00522                         else
00523                             tmask = DTK_ALL_SECS_M;
00524                         break;
00525 
00526                     case DTK_MINUTE:
00527                         tm->tm_min += val;
00528                         AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
00529                         tmask = DTK_M(MINUTE);
00530                         break;
00531 
00532                     case DTK_HOUR:
00533                         tm->tm_hour += val;
00534                         AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
00535                         tmask = DTK_M(HOUR);
00536                         type = DTK_DAY;
00537                         break;
00538 
00539                     case DTK_DAY:
00540                         tm->tm_mday += val;
00541                         AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
00542                         tmask = (fmask & DTK_M(DAY)) ? 0 : DTK_M(DAY);
00543                         break;
00544 
00545                     case DTK_WEEK:
00546                         tm->tm_mday += val * 7;
00547                         AdjustFractDays(fval, tm, fsec, 7);
00548                         tmask = (fmask & DTK_M(DAY)) ? 0 : DTK_M(DAY);
00549                         break;
00550 
00551                     case DTK_MONTH:
00552                         tm->tm_mon += val;
00553                         AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
00554                         tmask = DTK_M(MONTH);
00555                         break;
00556 
00557                     case DTK_YEAR:
00558                         tm->tm_year += val;
00559                         if (fval != 0)
00560                             tm->tm_mon += fval * MONTHS_PER_YEAR;
00561                         tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
00562                         break;
00563 
00564                     case DTK_DECADE:
00565                         tm->tm_year += val * 10;
00566                         if (fval != 0)
00567                             tm->tm_mon += fval * MONTHS_PER_YEAR * 10;
00568                         tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
00569                         break;
00570 
00571                     case DTK_CENTURY:
00572                         tm->tm_year += val * 100;
00573                         if (fval != 0)
00574                             tm->tm_mon += fval * MONTHS_PER_YEAR * 100;
00575                         tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
00576                         break;
00577 
00578                     case DTK_MILLENNIUM:
00579                         tm->tm_year += val * 1000;
00580                         if (fval != 0)
00581                             tm->tm_mon += fval * MONTHS_PER_YEAR * 1000;
00582                         tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
00583                         break;
00584 
00585                     default:
00586                         return DTERR_BAD_FORMAT;
00587                 }
00588                 break;
00589 
00590             case DTK_STRING:
00591             case DTK_SPECIAL:
00592                 type = DecodeUnits(i, field[i], &val);
00593                 if (type == IGNORE_DTF)
00594                     continue;
00595 
00596                 tmask = 0;      /* DTK_M(type); */
00597                 switch (type)
00598                 {
00599                     case UNITS:
00600                         type = val;
00601                         break;
00602 
00603                     case AGO:
00604                         is_before = TRUE;
00605                         type = val;
00606                         break;
00607 
00608                     case RESERV:
00609                         tmask = (DTK_DATE_M | DTK_TIME_M);
00610                         *dtype = val;
00611                         break;
00612 
00613                     default:
00614                         return DTERR_BAD_FORMAT;
00615                 }
00616                 break;
00617 
00618             default:
00619                 return DTERR_BAD_FORMAT;
00620         }
00621 
00622         if (tmask & fmask)
00623             return DTERR_BAD_FORMAT;
00624         fmask |= tmask;
00625     }
00626 
00627     /* ensure that at least one time field has been found */
00628     if (fmask == 0)
00629         return DTERR_BAD_FORMAT;
00630 
00631     /* ensure fractional seconds are fractional */
00632     if (*fsec != 0)
00633     {
00634         int         sec;
00635 
00636 #ifdef HAVE_INT64_TIMESTAMP
00637         sec = *fsec / USECS_PER_SEC;
00638         *fsec -= sec * USECS_PER_SEC;
00639 #else
00640         TMODULO(*fsec, sec, 1.0);
00641 #endif
00642         tm->tm_sec += sec;
00643     }
00644 
00645     /*----------
00646      * The SQL standard defines the interval literal
00647      *   '-1 1:00:00'
00648      * to mean "negative 1 days and negative 1 hours", while Postgres
00649      * traditionally treats this as meaning "negative 1 days and positive
00650      * 1 hours".  In SQL_STANDARD intervalstyle, we apply the leading sign
00651      * to all fields if there are no other explicit signs.
00652      *
00653      * We leave the signs alone if there are additional explicit signs.
00654      * This protects us against misinterpreting postgres-style dump output,
00655      * since the postgres-style output code has always put an explicit sign on
00656      * all fields following a negative field.  But note that SQL-spec output
00657      * is ambiguous and can be misinterpreted on load!  (So it's best practice
00658      * to dump in postgres style, not SQL style.)
00659      *----------
00660      */
00661     if (IntervalStyle == INTSTYLE_SQL_STANDARD && *field[0] == '-')
00662     {
00663         /* Check for additional explicit signs */
00664         bool        more_signs = false;
00665 
00666         for (i = 1; i < nf; i++)
00667         {
00668             if (*field[i] == '-' || *field[i] == '+')
00669             {
00670                 more_signs = true;
00671                 break;
00672             }
00673         }
00674 
00675         if (!more_signs)
00676         {
00677             /*
00678              * Rather than re-determining which field was field[0], just force
00679              * 'em all negative.
00680              */
00681             if (*fsec > 0)
00682                 *fsec = -(*fsec);
00683             if (tm->tm_sec > 0)
00684                 tm->tm_sec = -tm->tm_sec;
00685             if (tm->tm_min > 0)
00686                 tm->tm_min = -tm->tm_min;
00687             if (tm->tm_hour > 0)
00688                 tm->tm_hour = -tm->tm_hour;
00689             if (tm->tm_mday > 0)
00690                 tm->tm_mday = -tm->tm_mday;
00691             if (tm->tm_mon > 0)
00692                 tm->tm_mon = -tm->tm_mon;
00693             if (tm->tm_year > 0)
00694                 tm->tm_year = -tm->tm_year;
00695         }
00696     }
00697 
00698     /* finally, AGO negates everything */
00699     if (is_before)
00700     {
00701         *fsec = -(*fsec);
00702         tm->tm_sec = -tm->tm_sec;
00703         tm->tm_min = -tm->tm_min;
00704         tm->tm_hour = -tm->tm_hour;
00705         tm->tm_mday = -tm->tm_mday;
00706         tm->tm_mon = -tm->tm_mon;
00707         tm->tm_year = -tm->tm_year;
00708     }
00709 
00710     return 0;
00711 }
00712 
00713 
00714 /* copy&pasted from .../src/backend/utils/adt/datetime.c */
00715 static char *
00716 AddVerboseIntPart(char *cp, int value, const char *units,
00717                   bool *is_zero, bool *is_before)
00718 {
00719     if (value == 0)
00720         return cp;
00721     /* first nonzero value sets is_before */
00722     if (*is_zero)
00723     {
00724         *is_before = (value < 0);
00725         value = abs(value);
00726     }
00727     else if (*is_before)
00728         value = -value;
00729     sprintf(cp, " %d %s%s", value, units, (value == 1) ? "" : "s");
00730     *is_zero = FALSE;
00731     return cp + strlen(cp);
00732 }
00733 
00734 /* copy&pasted from .../src/backend/utils/adt/datetime.c */
00735 static char *
00736 AddPostgresIntPart(char *cp, int value, const char *units,
00737                    bool *is_zero, bool *is_before)
00738 {
00739     if (value == 0)
00740         return cp;
00741     sprintf(cp, "%s%s%d %s%s",
00742             (!*is_zero) ? " " : "",
00743             (*is_before && value > 0) ? "+" : "",
00744             value,
00745             units,
00746             (value != 1) ? "s" : "");
00747 
00748     /*
00749      * Each nonzero field sets is_before for (only) the next one.  This is a
00750      * tad bizarre but it's how it worked before...
00751      */
00752     *is_before = (value < 0);
00753     *is_zero = FALSE;
00754     return cp + strlen(cp);
00755 }
00756 
00757 /* copy&pasted from .../src/backend/utils/adt/datetime.c */
00758 static char *
00759 AddISO8601IntPart(char *cp, int value, char units)
00760 {
00761     if (value == 0)
00762         return cp;
00763     sprintf(cp, "%d%c", value, units);
00764     return cp + strlen(cp);
00765 }
00766 
00767 /* copy&pasted from .../src/backend/utils/adt/datetime.c */
00768 static void
00769 AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros)
00770 {
00771     if (fsec == 0)
00772     {
00773         if (fillzeros)
00774             sprintf(cp, "%02d", abs(sec));
00775         else
00776             sprintf(cp, "%d", abs(sec));
00777     }
00778     else
00779     {
00780 #ifdef HAVE_INT64_TIMESTAMP
00781         if (fillzeros)
00782             sprintf(cp, "%02d.%0*d", abs(sec), precision, (int) Abs(fsec));
00783         else
00784             sprintf(cp, "%d.%0*d", abs(sec), precision, (int) Abs(fsec));
00785 #else
00786         if (fillzeros)
00787             sprintf(cp, "%0*.*f", precision + 3, precision, fabs(sec + fsec));
00788         else
00789             sprintf(cp, "%.*f", precision, fabs(sec + fsec));
00790 #endif
00791         TrimTrailingZeros(cp);
00792     }
00793 }
00794 
00795 
00796 /* copy&pasted from .../src/backend/utils/adt/datetime.c
00797  *
00798  * Change pg_tm to tm
00799  */
00800 
00801 int
00802 EncodeInterval(struct /* pg_ */ tm * tm, fsec_t fsec, int style, char *str)
00803 {
00804 
00805     char       *cp = str;
00806     int         year = tm->tm_year;
00807     int         mon = tm->tm_mon;
00808     int         mday = tm->tm_mday;
00809     int         hour = tm->tm_hour;
00810     int         min = tm->tm_min;
00811     int         sec = tm->tm_sec;
00812     bool        is_before = FALSE;
00813     bool        is_zero = TRUE;
00814 
00815     /*
00816      * The sign of year and month are guaranteed to match, since they are
00817      * stored internally as "month". But we'll need to check for is_before and
00818      * is_zero when determining the signs of day and hour/minute/seconds
00819      * fields.
00820      */
00821     switch (style)
00822     {
00823             /* SQL Standard interval format */
00824         case INTSTYLE_SQL_STANDARD:
00825             {
00826                 bool        has_negative = year < 0 || mon < 0 ||
00827                 mday < 0 || hour < 0 ||
00828                 min < 0 || sec < 0 || fsec < 0;
00829                 bool        has_positive = year > 0 || mon > 0 ||
00830                 mday > 0 || hour > 0 ||
00831                 min > 0 || sec > 0 || fsec > 0;
00832                 bool        has_year_month = year != 0 || mon != 0;
00833                 bool        has_day_time = mday != 0 || hour != 0 ||
00834                 min != 0 || sec != 0 || fsec != 0;
00835                 bool        has_day = mday != 0;
00836                 bool        sql_standard_value = !(has_negative && has_positive) &&
00837                 !(has_year_month && has_day_time);
00838 
00839                 /*
00840                  * SQL Standard wants only 1 "<sign>" preceding the whole
00841                  * interval ... but can't do that if mixed signs.
00842                  */
00843                 if (has_negative && sql_standard_value)
00844                 {
00845                     *cp++ = '-';
00846                     year = -year;
00847                     mon = -mon;
00848                     mday = -mday;
00849                     hour = -hour;
00850                     min = -min;
00851                     sec = -sec;
00852                     fsec = -fsec;
00853                 }
00854 
00855                 if (!has_negative && !has_positive)
00856                 {
00857                     sprintf(cp, "0");
00858                 }
00859                 else if (!sql_standard_value)
00860                 {
00861                     /*
00862                      * For non sql-standard interval values, force outputting
00863                      * the signs to avoid ambiguities with intervals with
00864                      * mixed sign components.
00865                      */
00866                     char        year_sign = (year < 0 || mon < 0) ? '-' : '+';
00867                     char        day_sign = (mday < 0) ? '-' : '+';
00868                     char        sec_sign = (hour < 0 || min < 0 ||
00869                                             sec < 0 || fsec < 0) ? '-' : '+';
00870 
00871                     sprintf(cp, "%c%d-%d %c%d %c%d:%02d:",
00872                             year_sign, abs(year), abs(mon),
00873                             day_sign, abs(mday),
00874                             sec_sign, abs(hour), abs(min));
00875                     cp += strlen(cp);
00876                     AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
00877                 }
00878                 else if (has_year_month)
00879                 {
00880                     sprintf(cp, "%d-%d", year, mon);
00881                 }
00882                 else if (has_day)
00883                 {
00884                     sprintf(cp, "%d %d:%02d:", mday, hour, min);
00885                     cp += strlen(cp);
00886                     AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
00887                 }
00888                 else
00889                 {
00890                     sprintf(cp, "%d:%02d:", hour, min);
00891                     cp += strlen(cp);
00892                     AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
00893                 }
00894             }
00895             break;
00896 
00897             /* ISO 8601 "time-intervals by duration only" */
00898         case INTSTYLE_ISO_8601:
00899             /* special-case zero to avoid printing nothing */
00900             if (year == 0 && mon == 0 && mday == 0 &&
00901                 hour == 0 && min == 0 && sec == 0 && fsec == 0)
00902             {
00903                 sprintf(cp, "PT0S");
00904                 break;
00905             }
00906             *cp++ = 'P';
00907             cp = AddISO8601IntPart(cp, year, 'Y');
00908             cp = AddISO8601IntPart(cp, mon, 'M');
00909             cp = AddISO8601IntPart(cp, mday, 'D');
00910             if (hour != 0 || min != 0 || sec != 0 || fsec != 0)
00911                 *cp++ = 'T';
00912             cp = AddISO8601IntPart(cp, hour, 'H');
00913             cp = AddISO8601IntPart(cp, min, 'M');
00914             if (sec != 0 || fsec != 0)
00915             {
00916                 if (sec < 0 || fsec < 0)
00917                     *cp++ = '-';
00918                 AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
00919                 cp += strlen(cp);
00920                 *cp++ = 'S';
00921                 *cp = '\0';
00922             }
00923             break;
00924 
00925             /* Compatible with postgresql < 8.4 when DateStyle = 'iso' */
00926         case INTSTYLE_POSTGRES:
00927             cp = AddPostgresIntPart(cp, year, "year", &is_zero, &is_before);
00928             cp = AddPostgresIntPart(cp, mon, "mon", &is_zero, &is_before);
00929             cp = AddPostgresIntPart(cp, mday, "day", &is_zero, &is_before);
00930             if (is_zero || hour != 0 || min != 0 || sec != 0 || fsec != 0)
00931             {
00932                 bool        minus = (hour < 0 || min < 0 || sec < 0 || fsec < 0);
00933 
00934                 sprintf(cp, "%s%s%02d:%02d:",
00935                         is_zero ? "" : " ",
00936                         (minus ? "-" : (is_before ? "+" : "")),
00937                         abs(hour), abs(min));
00938                 cp += strlen(cp);
00939                 AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
00940             }
00941             break;
00942 
00943             /* Compatible with postgresql < 8.4 when DateStyle != 'iso' */
00944         case INTSTYLE_POSTGRES_VERBOSE:
00945         default:
00946             strcpy(cp, "@");
00947             cp++;
00948             cp = AddVerboseIntPart(cp, year, "year", &is_zero, &is_before);
00949             cp = AddVerboseIntPart(cp, mon, "mon", &is_zero, &is_before);
00950             cp = AddVerboseIntPart(cp, mday, "day", &is_zero, &is_before);
00951             cp = AddVerboseIntPart(cp, hour, "hour", &is_zero, &is_before);
00952             cp = AddVerboseIntPart(cp, min, "min", &is_zero, &is_before);
00953             if (sec != 0 || fsec != 0)
00954             {
00955                 *cp++ = ' ';
00956                 if (sec < 0 || (sec == 0 && fsec < 0))
00957                 {
00958                     if (is_zero)
00959                         is_before = TRUE;
00960                     else if (!is_before)
00961                         *cp++ = '-';
00962                 }
00963                 else if (is_before)
00964                     *cp++ = '-';
00965                 AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
00966                 cp += strlen(cp);
00967                 sprintf(cp, " sec%s",
00968                         (abs(sec) != 1 || fsec != 0) ? "s" : "");
00969                 is_zero = FALSE;
00970             }
00971             /* identically zero? then put in a unitless zero... */
00972             if (is_zero)
00973                 strcat(cp, " 0");
00974             if (is_before)
00975                 strcat(cp, " ago");
00976             break;
00977     }
00978 
00979     return 0;
00980 }   /* EncodeInterval() */
00981 
00982 
00983 /* interval2tm()
00984  * Convert a interval data type to a tm structure.
00985  */
00986 static int
00987 interval2tm(interval span, struct tm * tm, fsec_t *fsec)
00988 {
00989 #ifdef HAVE_INT64_TIMESTAMP
00990     int64       time;
00991 #else
00992     double      time;
00993 #endif
00994 
00995     if (span.month != 0)
00996     {
00997         tm->tm_year = span.month / MONTHS_PER_YEAR;
00998         tm->tm_mon = span.month % MONTHS_PER_YEAR;
00999 
01000     }
01001     else
01002     {
01003         tm->tm_year = 0;
01004         tm->tm_mon = 0;
01005     }
01006 
01007     time = span.time;
01008 
01009 #ifdef HAVE_INT64_TIMESTAMP
01010     tm->tm_mday = time / USECS_PER_DAY;
01011     time -= tm->tm_mday * USECS_PER_DAY;
01012     tm->tm_hour = time / USECS_PER_HOUR;
01013     time -= tm->tm_hour * USECS_PER_HOUR;
01014     tm->tm_min = time / USECS_PER_MINUTE;
01015     time -= tm->tm_min * USECS_PER_MINUTE;
01016     tm->tm_sec = time / USECS_PER_SEC;
01017     *fsec = time - (tm->tm_sec * USECS_PER_SEC);
01018 #else
01019 recalc:
01020     TMODULO(time, tm->tm_mday, (double) SECS_PER_DAY);
01021     TMODULO(time, tm->tm_hour, (double) SECS_PER_HOUR);
01022     TMODULO(time, tm->tm_min, (double) SECS_PER_MINUTE);
01023     TMODULO(time, tm->tm_sec, 1.0);
01024     time = TSROUND(time);
01025     /* roundoff may need to propagate to higher-order fields */
01026     if (time >= 1.0)
01027     {
01028         time = ceil(span.time);
01029         goto recalc;
01030     }
01031     *fsec = time;
01032 #endif
01033 
01034     return 0;
01035 }   /* interval2tm() */
01036 
01037 static int
01038 tm2interval(struct tm * tm, fsec_t fsec, interval * span)
01039 {
01040     span->month = tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon;
01041 #ifdef HAVE_INT64_TIMESTAMP
01042     span->time = (((((((tm->tm_mday * INT64CONST(24)) +
01043                        tm->tm_hour) * INT64CONST(60)) +
01044                      tm->tm_min) * INT64CONST(60)) +
01045                    tm->tm_sec) * USECS_PER_SEC) + fsec;
01046 #else
01047     span->time = (((((tm->tm_mday * (double) HOURS_PER_DAY) +
01048                      tm->tm_hour) * (double) MINS_PER_HOUR) +
01049                    tm->tm_min) * (double) SECS_PER_MINUTE) +
01050         tm->tm_sec + fsec;
01051 #endif
01052 
01053     return 0;
01054 }   /* tm2interval() */
01055 
01056 interval *
01057 PGTYPESinterval_new(void)
01058 {
01059     interval   *result;
01060 
01061     result = (interval *) pgtypes_alloc(sizeof(interval));
01062     /* result can be NULL if we run out of memory */
01063     return result;
01064 }
01065 
01066 void
01067 PGTYPESinterval_free(interval * intvl)
01068 {
01069     free(intvl);
01070 }
01071 
01072 interval *
01073 PGTYPESinterval_from_asc(char *str, char **endptr)
01074 {
01075     interval   *result = NULL;
01076     fsec_t      fsec;
01077     struct tm   tt,
01078                *tm = &tt;
01079     int         dtype;
01080     int         nf;
01081     char       *field[MAXDATEFIELDS];
01082     int         ftype[MAXDATEFIELDS];
01083     char        lowstr[MAXDATELEN + MAXDATEFIELDS];
01084     char       *realptr;
01085     char      **ptr = (endptr != NULL) ? endptr : &realptr;
01086 
01087     tm->tm_year = 0;
01088     tm->tm_mon = 0;
01089     tm->tm_mday = 0;
01090     tm->tm_hour = 0;
01091     tm->tm_min = 0;
01092     tm->tm_sec = 0;
01093     fsec = 0;
01094 
01095     if (strlen(str) >= sizeof(lowstr))
01096     {
01097         errno = PGTYPES_INTVL_BAD_INTERVAL;
01098         return NULL;
01099     }
01100 
01101     if (ParseDateTime(str, lowstr, field, ftype, &nf, ptr) != 0 ||
01102         (DecodeInterval(field, ftype, nf, &dtype, tm, &fsec) != 0 &&
01103          DecodeISO8601Interval(str, &dtype, tm, &fsec) != 0))
01104     {
01105         errno = PGTYPES_INTVL_BAD_INTERVAL;
01106         return NULL;
01107     }
01108 
01109     result = (interval *) pgtypes_alloc(sizeof(interval));
01110     if (!result)
01111         return NULL;
01112 
01113     if (dtype != DTK_DELTA)
01114     {
01115         errno = PGTYPES_INTVL_BAD_INTERVAL;
01116         free(result);
01117         return NULL;
01118     }
01119 
01120     if (tm2interval(tm, fsec, result) != 0)
01121     {
01122         errno = PGTYPES_INTVL_BAD_INTERVAL;
01123         free(result);
01124         return NULL;
01125     }
01126 
01127     errno = 0;
01128     return result;
01129 }
01130 
01131 char *
01132 PGTYPESinterval_to_asc(interval * span)
01133 {
01134     struct tm   tt,
01135                *tm = &tt;
01136     fsec_t      fsec;
01137     char        buf[MAXDATELEN + 1];
01138     int         IntervalStyle = INTSTYLE_POSTGRES_VERBOSE;
01139 
01140     if (interval2tm(*span, tm, &fsec) != 0)
01141     {
01142         errno = PGTYPES_INTVL_BAD_INTERVAL;
01143         return NULL;
01144     }
01145 
01146     if (EncodeInterval(tm, fsec, IntervalStyle, buf) != 0)
01147     {
01148         errno = PGTYPES_INTVL_BAD_INTERVAL;
01149         return NULL;
01150     }
01151 
01152     return pgtypes_strdup(buf);
01153 }
01154 
01155 int
01156 PGTYPESinterval_copy(interval * intvlsrc, interval * intvldest)
01157 {
01158     intvldest->time = intvlsrc->time;
01159     intvldest->month = intvlsrc->month;
01160 
01161     return 0;
01162 }