Header And Logo

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

Functions

interval.c File Reference

#include "postgres_fe.h"
#include <time.h>
#include <math.h>
#include <limits.h>
#include "extern.h"
#include "dt.h"
#include "pgtypes_error.h"
#include "pgtypes_interval.h"
Include dependency graph for interval.c:

Go to the source code of this file.

Functions

static int strtoi (const char *nptr, char **endptr, int base)
static void AdjustFractSeconds (double frac, structtm *tm, fsec_t *fsec, int scale)
static void AdjustFractDays (double frac, structtm *tm, fsec_t *fsec, int scale)
static int ParseISO8601Number (char *str, char **endptr, int *ipart, double *fpart)
static int ISO8601IntegerWidth (char *fieldstart)
static void ClearPgTm (structtm *tm, fsec_t *fsec)
static int DecodeISO8601Interval (char *str, int *dtype, structtm *tm, fsec_t *fsec)
int DecodeInterval (char **field, int *ftype, int nf, int *dtype, structtm *tm, fsec_t *fsec)
static char * AddVerboseIntPart (char *cp, int value, const char *units, bool *is_zero, bool *is_before)
static char * AddPostgresIntPart (char *cp, int value, const char *units, bool *is_zero, bool *is_before)
static char * AddISO8601IntPart (char *cp, int value, char units)
static void AppendSeconds (char *cp, int sec, fsec_t fsec, int precision, bool fillzeros)
int EncodeInterval (structtm *tm, fsec_t fsec, int style, char *str)
static int interval2tm (interval span, struct tm *tm, fsec_t *fsec)
static int tm2interval (struct tm *tm, fsec_t fsec, interval *span)
intervalPGTYPESinterval_new (void)
void PGTYPESinterval_free (interval *intvl)
intervalPGTYPESinterval_from_asc (char *str, char **endptr)
char * PGTYPESinterval_to_asc (interval *span)
int PGTYPESinterval_copy (interval *intvlsrc, interval *intvldest)

Function Documentation

static char* AddISO8601IntPart ( char *  cp,
int  value,
char  units 
) [static]

Definition at line 759 of file interval.c.

Referenced by EncodeInterval().

{
    if (value == 0)
        return cp;
    sprintf(cp, "%d%c", value, units);
    return cp + strlen(cp);
}

static char* AddPostgresIntPart ( char *  cp,
int  value,
const char *  units,
bool is_zero,
bool is_before 
) [static]

Definition at line 736 of file interval.c.

Referenced by EncodeInterval().

{
    if (value == 0)
        return cp;
    sprintf(cp, "%s%s%d %s%s",
            (!*is_zero) ? " " : "",
            (*is_before && value > 0) ? "+" : "",
            value,
            units,
            (value != 1) ? "s" : "");

    /*
     * Each nonzero field sets is_before for (only) the next one.  This is a
     * tad bizarre but it's how it worked before...
     */
    *is_before = (value < 0);
    *is_zero = FALSE;
    return cp + strlen(cp);
}

static char* AddVerboseIntPart ( char *  cp,
int  value,
const char *  units,
bool is_zero,
bool is_before 
) [static]

Definition at line 716 of file interval.c.

Referenced by EncodeInterval().

{
    if (value == 0)
        return cp;
    /* first nonzero value sets is_before */
    if (*is_zero)
    {
        *is_before = (value < 0);
        value = abs(value);
    }
    else if (*is_before)
        value = -value;
    sprintf(cp, " %d %s%s", value, units, (value == 1) ? "" : "s");
    *is_zero = FALSE;
    return cp + strlen(cp);
}

static void AdjustFractDays ( double  frac,
struct tm tm,
fsec_t fsec,
int  scale 
) [static]

Definition at line 57 of file interval.c.

References AdjustFractSeconds(), and SECS_PER_DAY.

Referenced by DecodeInterval(), and DecodeISO8601Interval().

{
    int         extra_days;

    if (frac == 0)
        return;
    frac *= scale;
    extra_days = (int) frac;
    tm->tm_mday += extra_days;
    frac -= extra_days;
    AdjustFractSeconds(frac, tm, fsec, SECS_PER_DAY);
}

static void AdjustFractSeconds ( double  frac,
struct tm tm,
fsec_t fsec,
int  scale 
) [static]

Definition at line 35 of file interval.c.

References rint().

Referenced by AdjustFractDays(), DecodeInterval(), and DecodeISO8601Interval().

{
    int         sec;

    if (frac == 0)
        return;
    frac *= scale;
    sec = (int) frac;
    tm->tm_sec += sec;
    frac -= sec;
#ifdef HAVE_INT64_TIMESTAMP
    *fsec += rint(frac * 1000000);
#else
    *fsec += frac;
#endif
}

static void AppendSeconds ( char *  cp,
int  sec,
fsec_t  fsec,
int  precision,
bool  fillzeros 
) [static]

Definition at line 769 of file interval.c.

References Abs, and TrimTrailingZeros().

Referenced by EncodeInterval().

{
    if (fsec == 0)
    {
        if (fillzeros)
            sprintf(cp, "%02d", abs(sec));
        else
            sprintf(cp, "%d", abs(sec));
    }
    else
    {
#ifdef HAVE_INT64_TIMESTAMP
        if (fillzeros)
            sprintf(cp, "%02d.%0*d", abs(sec), precision, (int) Abs(fsec));
        else
            sprintf(cp, "%d.%0*d", abs(sec), precision, (int) Abs(fsec));
#else
        if (fillzeros)
            sprintf(cp, "%0*.*f", precision + 3, precision, fabs(sec + fsec));
        else
            sprintf(cp, "%.*f", precision, fabs(sec + fsec));
#endif
        TrimTrailingZeros(cp);
    }
}

static void ClearPgTm ( struct tm tm,
fsec_t fsec 
) [inline, static]

Definition at line 110 of file interval.c.

Referenced by DecodeInterval(), and DecodeISO8601Interval().

{
    tm->tm_year = 0;
    tm->tm_mon = 0;
    tm->tm_mday = 0;
    tm->tm_hour = 0;
    tm->tm_min = 0;
    tm->tm_sec = 0;
    *fsec = 0;
}

int DecodeInterval ( char **  field,
int *  ftype,
int  nf,
int *  dtype,
struct tm tm,
fsec_t fsec 
)

Definition at line 342 of file interval.c.

References AdjustFractDays(), AdjustFractSeconds(), AGO, ClearPgTm(), DAY, DAYS_PER_MONTH, DecodeTime(), DecodeUnits(), DTK_CENTURY, DTK_DATE, DTK_DATE_M, DTK_DAY, DTK_DECADE, DTK_HOUR, DTK_M, DTK_MICROSEC, DTK_MILLENNIUM, DTK_MILLISEC, DTK_MINUTE, DTK_MONTH, DTK_NUMBER, DTK_SECOND, DTK_SPECIAL, DTK_STRING, DTK_TIME, DTK_TZ, DTK_WEEK, DTK_YEAR, HOUR, i, IGNORE_DTF, INTERVAL_MASK, IntervalStyle, INTSTYLE_SQL_STANDARD, MICROSECOND, MILLISECOND, MINUTE, MONTH, MONTHS_PER_YEAR, NULL, range(), RESERV, rint(), SECOND, SECS_PER_DAY, SECS_PER_HOUR, SECS_PER_MINUTE, strtoi(), TMODULO, TZ, UNITS, val, and YEAR.

{
    int         IntervalStyle = INTSTYLE_POSTGRES_VERBOSE;
    int         range = INTERVAL_FULL_RANGE;
    bool        is_before = FALSE;
    char       *cp;
    int         fmask = 0,
                tmask,
                type;
    int         i;
    int         dterr;
    int         val;
    double      fval;

    *dtype = DTK_DELTA;
    type = IGNORE_DTF;
    ClearPgTm(tm, fsec);

    /* read through list backwards to pick up units before values */
    for (i = nf - 1; i >= 0; i--)
    {
        switch (ftype[i])
        {
            case DTK_TIME:
                dterr = DecodeTime(field[i],    /* range, */
                                   &tmask, tm, fsec);
                if (dterr)
                    return dterr;
                type = DTK_DAY;
                break;

            case DTK_TZ:

                /*
                 * Timezone is a token with a leading sign character and at
                 * least one digit; there could be ':', '.', '-' embedded in
                 * it as well.
                 */
                /* Assert(*field[i] == '-' || *field[i] == '+'); */

                /*
                 * Try for hh:mm or hh:mm:ss.  If not, fall through to
                 * DTK_NUMBER case, which can handle signed float numbers and
                 * signed year-month values.
                 */
                if (strchr(field[i] + 1, ':') != NULL &&
                    DecodeTime(field[i] + 1,    /* INTERVAL_FULL_RANGE, */
                               &tmask, tm, fsec) == 0)
                {
                    if (*field[i] == '-')
                    {
                        /* flip the sign on all fields */
                        tm->tm_hour = -tm->tm_hour;
                        tm->tm_min = -tm->tm_min;
                        tm->tm_sec = -tm->tm_sec;
                        *fsec = -(*fsec);
                    }

                    /*
                     * Set the next type to be a day, if units are not
                     * specified. This handles the case of '1 +02:03' since we
                     * are reading right to left.
                     */
                    type = DTK_DAY;
                    tmask = DTK_M(TZ);
                    break;
                }
                /* FALL THROUGH */

            case DTK_DATE:
            case DTK_NUMBER:
                if (type == IGNORE_DTF)
                {
                    /* use typmod to decide what rightmost field is */
                    switch (range)
                    {
                        case INTERVAL_MASK(YEAR):
                            type = DTK_YEAR;
                            break;
                        case INTERVAL_MASK(MONTH):
                        case INTERVAL_MASK(YEAR) | INTERVAL_MASK(MONTH):
                            type = DTK_MONTH;
                            break;
                        case INTERVAL_MASK(DAY):
                            type = DTK_DAY;
                            break;
                        case INTERVAL_MASK(HOUR):
                        case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR):
                        case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE):
                        case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
                            type = DTK_HOUR;
                            break;
                        case INTERVAL_MASK(MINUTE):
                        case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE):
                            type = DTK_MINUTE;
                            break;
                        case INTERVAL_MASK(SECOND):
                        case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
                        case INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
                            type = DTK_SECOND;
                            break;
                        default:
                            type = DTK_SECOND;
                            break;
                    }
                }

                errno = 0;
                val = strtoi(field[i], &cp, 10);
                if (errno == ERANGE)
                    return DTERR_FIELD_OVERFLOW;

                if (*cp == '-')
                {
                    /* SQL "years-months" syntax */
                    int         val2;

                    val2 = strtoi(cp + 1, &cp, 10);
                    if (errno == ERANGE || val2 < 0 || val2 >= MONTHS_PER_YEAR)
                        return DTERR_FIELD_OVERFLOW;
                    if (*cp != '\0')
                        return DTERR_BAD_FORMAT;
                    type = DTK_MONTH;
                    if (*field[i] == '-')
                        val2 = -val2;
                    val = val * MONTHS_PER_YEAR + val2;
                    fval = 0;
                }
                else if (*cp == '.')
                {
                    errno = 0;
                    fval = strtod(cp, &cp);
                    if (*cp != '\0' || errno != 0)
                        return DTERR_BAD_FORMAT;

                    if (*field[i] == '-')
                        fval = -fval;
                }
                else if (*cp == '\0')
                    fval = 0;
                else
                    return DTERR_BAD_FORMAT;

                tmask = 0;      /* DTK_M(type); */

                switch (type)
                {
                    case DTK_MICROSEC:
#ifdef HAVE_INT64_TIMESTAMP
                        *fsec += rint(val + fval);
#else
                        *fsec += (val + fval) * 1e-6;
#endif
                        tmask = DTK_M(MICROSECOND);
                        break;

                    case DTK_MILLISEC:
#ifdef HAVE_INT64_TIMESTAMP
                        *fsec += rint((val + fval) * 1000);
#else
                        *fsec += (val + fval) * 1e-3;
#endif
                        tmask = DTK_M(MILLISECOND);
                        break;

                    case DTK_SECOND:
                        tm->tm_sec += val;
#ifdef HAVE_INT64_TIMESTAMP
                        *fsec += rint(fval * 1000000);
#else
                        *fsec += fval;
#endif

                        /*
                         * If any subseconds were specified, consider this
                         * microsecond and millisecond input as well.
                         */
                        if (fval == 0)
                            tmask = DTK_M(SECOND);
                        else
                            tmask = DTK_ALL_SECS_M;
                        break;

                    case DTK_MINUTE:
                        tm->tm_min += val;
                        AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
                        tmask = DTK_M(MINUTE);
                        break;

                    case DTK_HOUR:
                        tm->tm_hour += val;
                        AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
                        tmask = DTK_M(HOUR);
                        type = DTK_DAY;
                        break;

                    case DTK_DAY:
                        tm->tm_mday += val;
                        AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
                        tmask = (fmask & DTK_M(DAY)) ? 0 : DTK_M(DAY);
                        break;

                    case DTK_WEEK:
                        tm->tm_mday += val * 7;
                        AdjustFractDays(fval, tm, fsec, 7);
                        tmask = (fmask & DTK_M(DAY)) ? 0 : DTK_M(DAY);
                        break;

                    case DTK_MONTH:
                        tm->tm_mon += val;
                        AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
                        tmask = DTK_M(MONTH);
                        break;

                    case DTK_YEAR:
                        tm->tm_year += val;
                        if (fval != 0)
                            tm->tm_mon += fval * MONTHS_PER_YEAR;
                        tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
                        break;

                    case DTK_DECADE:
                        tm->tm_year += val * 10;
                        if (fval != 0)
                            tm->tm_mon += fval * MONTHS_PER_YEAR * 10;
                        tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
                        break;

                    case DTK_CENTURY:
                        tm->tm_year += val * 100;
                        if (fval != 0)
                            tm->tm_mon += fval * MONTHS_PER_YEAR * 100;
                        tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
                        break;

                    case DTK_MILLENNIUM:
                        tm->tm_year += val * 1000;
                        if (fval != 0)
                            tm->tm_mon += fval * MONTHS_PER_YEAR * 1000;
                        tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
                        break;

                    default:
                        return DTERR_BAD_FORMAT;
                }
                break;

            case DTK_STRING:
            case DTK_SPECIAL:
                type = DecodeUnits(i, field[i], &val);
                if (type == IGNORE_DTF)
                    continue;

                tmask = 0;      /* DTK_M(type); */
                switch (type)
                {
                    case UNITS:
                        type = val;
                        break;

                    case AGO:
                        is_before = TRUE;
                        type = val;
                        break;

                    case RESERV:
                        tmask = (DTK_DATE_M | DTK_TIME_M);
                        *dtype = val;
                        break;

                    default:
                        return DTERR_BAD_FORMAT;
                }
                break;

            default:
                return DTERR_BAD_FORMAT;
        }

        if (tmask & fmask)
            return DTERR_BAD_FORMAT;
        fmask |= tmask;
    }

    /* ensure that at least one time field has been found */
    if (fmask == 0)
        return DTERR_BAD_FORMAT;

    /* ensure fractional seconds are fractional */
    if (*fsec != 0)
    {
        int         sec;

#ifdef HAVE_INT64_TIMESTAMP
        sec = *fsec / USECS_PER_SEC;
        *fsec -= sec * USECS_PER_SEC;
#else
        TMODULO(*fsec, sec, 1.0);
#endif
        tm->tm_sec += sec;
    }

    /*----------
     * The SQL standard defines the interval literal
     *   '-1 1:00:00'
     * to mean "negative 1 days and negative 1 hours", while Postgres
     * traditionally treats this as meaning "negative 1 days and positive
     * 1 hours".  In SQL_STANDARD intervalstyle, we apply the leading sign
     * to all fields if there are no other explicit signs.
     *
     * We leave the signs alone if there are additional explicit signs.
     * This protects us against misinterpreting postgres-style dump output,
     * since the postgres-style output code has always put an explicit sign on
     * all fields following a negative field.  But note that SQL-spec output
     * is ambiguous and can be misinterpreted on load!  (So it's best practice
     * to dump in postgres style, not SQL style.)
     *----------
     */
    if (IntervalStyle == INTSTYLE_SQL_STANDARD && *field[0] == '-')
    {
        /* Check for additional explicit signs */
        bool        more_signs = false;

        for (i = 1; i < nf; i++)
        {
            if (*field[i] == '-' || *field[i] == '+')
            {
                more_signs = true;
                break;
            }
        }

        if (!more_signs)
        {
            /*
             * Rather than re-determining which field was field[0], just force
             * 'em all negative.
             */
            if (*fsec > 0)
                *fsec = -(*fsec);
            if (tm->tm_sec > 0)
                tm->tm_sec = -tm->tm_sec;
            if (tm->tm_min > 0)
                tm->tm_min = -tm->tm_min;
            if (tm->tm_hour > 0)
                tm->tm_hour = -tm->tm_hour;
            if (tm->tm_mday > 0)
                tm->tm_mday = -tm->tm_mday;
            if (tm->tm_mon > 0)
                tm->tm_mon = -tm->tm_mon;
            if (tm->tm_year > 0)
                tm->tm_year = -tm->tm_year;
        }
    }

    /* finally, AGO negates everything */
    if (is_before)
    {
        *fsec = -(*fsec);
        tm->tm_sec = -tm->tm_sec;
        tm->tm_min = -tm->tm_min;
        tm->tm_hour = -tm->tm_hour;
        tm->tm_mday = -tm->tm_mday;
        tm->tm_mon = -tm->tm_mon;
        tm->tm_year = -tm->tm_year;
    }

    return 0;
}

static int DecodeISO8601Interval ( char *  str,
int *  dtype,
struct tm tm,
fsec_t fsec 
) [static]

Definition at line 128 of file interval.c.

References AdjustFractDays(), AdjustFractSeconds(), ClearPgTm(), DAYS_PER_MONTH, ISO8601IntegerWidth(), ParseISO8601Number(), SECS_PER_DAY, SECS_PER_HOUR, SECS_PER_MINUTE, and val.

{
    bool        datepart = true;
    bool        havefield = false;

    *dtype = DTK_DELTA;
    ClearPgTm(tm, fsec);

    if (strlen(str) < 2 || str[0] != 'P')
        return DTERR_BAD_FORMAT;

    str++;
    while (*str)
    {
        char       *fieldstart;
        int         val;
        double      fval;
        char        unit;
        int         dterr;

        if (*str == 'T')        /* T indicates the beginning of the time part */
        {
            datepart = false;
            havefield = false;
            str++;
            continue;
        }

        fieldstart = str;
        dterr = ParseISO8601Number(str, &str, &val, &fval);
        if (dterr)
            return dterr;

        /*
         * Note: we could step off the end of the string here.  Code below
         * *must* exit the loop if unit == '\0'.
         */
        unit = *str++;

        if (datepart)
        {
            switch (unit)       /* before T: Y M W D */
            {
                case 'Y':
                    tm->tm_year += val;
                    tm->tm_mon += (fval * 12);
                    break;
                case 'M':
                    tm->tm_mon += val;
                    AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
                    break;
                case 'W':
                    tm->tm_mday += val * 7;
                    AdjustFractDays(fval, tm, fsec, 7);
                    break;
                case 'D':
                    tm->tm_mday += val;
                    AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
                    break;
                case 'T':       /* ISO 8601 4.4.3.3 Alternative Format / Basic */
                case '\0':
                    if (ISO8601IntegerWidth(fieldstart) == 8 && !havefield)
                    {
                        tm->tm_year += val / 10000;
                        tm->tm_mon += (val / 100) % 100;
                        tm->tm_mday += val % 100;
                        AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
                        if (unit == '\0')
                            return 0;
                        datepart = false;
                        havefield = false;
                        continue;
                    }
                    /* Else fall through to extended alternative format */
                case '-':       /* ISO 8601 4.4.3.3 Alternative Format,
                                 * Extended */
                    if (havefield)
                        return DTERR_BAD_FORMAT;

                    tm->tm_year += val;
                    tm->tm_mon += (fval * 12);
                    if (unit == '\0')
                        return 0;
                    if (unit == 'T')
                    {
                        datepart = false;
                        havefield = false;
                        continue;
                    }

                    dterr = ParseISO8601Number(str, &str, &val, &fval);
                    if (dterr)
                        return dterr;
                    tm->tm_mon += val;
                    AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
                    if (*str == '\0')
                        return 0;
                    if (*str == 'T')
                    {
                        datepart = false;
                        havefield = false;
                        continue;
                    }
                    if (*str != '-')
                        return DTERR_BAD_FORMAT;
                    str++;

                    dterr = ParseISO8601Number(str, &str, &val, &fval);
                    if (dterr)
                        return dterr;
                    tm->tm_mday += val;
                    AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
                    if (*str == '\0')
                        return 0;
                    if (*str == 'T')
                    {
                        datepart = false;
                        havefield = false;
                        continue;
                    }
                    return DTERR_BAD_FORMAT;
                default:
                    /* not a valid date unit suffix */
                    return DTERR_BAD_FORMAT;
            }
        }
        else
        {
            switch (unit)       /* after T: H M S */
            {
                case 'H':
                    tm->tm_hour += val;
                    AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
                    break;
                case 'M':
                    tm->tm_min += val;
                    AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
                    break;
                case 'S':
                    tm->tm_sec += val;
                    AdjustFractSeconds(fval, tm, fsec, 1);
                    break;
                case '\0':      /* ISO 8601 4.4.3.3 Alternative Format */
                    if (ISO8601IntegerWidth(fieldstart) == 6 && !havefield)
                    {
                        tm->tm_hour += val / 10000;
                        tm->tm_min += (val / 100) % 100;
                        tm->tm_sec += val % 100;
                        AdjustFractSeconds(fval, tm, fsec, 1);
                        return 0;
                    }
                    /* Else fall through to extended alternative format */
                case ':':       /* ISO 8601 4.4.3.3 Alternative Format,
                                 * Extended */
                    if (havefield)
                        return DTERR_BAD_FORMAT;

                    tm->tm_hour += val;
                    AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
                    if (unit == '\0')
                        return 0;

                    dterr = ParseISO8601Number(str, &str, &val, &fval);
                    if (dterr)
                        return dterr;
                    tm->tm_min += val;
                    AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
                    if (*str == '\0')
                        return 0;
                    if (*str != ':')
                        return DTERR_BAD_FORMAT;
                    str++;

                    dterr = ParseISO8601Number(str, &str, &val, &fval);
                    if (dterr)
                        return dterr;
                    tm->tm_sec += val;
                    AdjustFractSeconds(fval, tm, fsec, 1);
                    if (*str == '\0')
                        return 0;
                    return DTERR_BAD_FORMAT;

                default:
                    /* not a valid time unit suffix */
                    return DTERR_BAD_FORMAT;
            }
        }

        havefield = true;
    }

    return 0;
}

int EncodeInterval ( struct tm tm,
fsec_t  fsec,
int  style,
char *  str 
)

Definition at line 802 of file interval.c.

References AddISO8601IntPart(), AddPostgresIntPart(), AddVerboseIntPart(), AppendSeconds(), INTSTYLE_ISO_8601, INTSTYLE_POSTGRES, INTSTYLE_POSTGRES_VERBOSE, INTSTYLE_SQL_STANDARD, and MAX_INTERVAL_PRECISION.

{

    char       *cp = str;
    int         year = tm->tm_year;
    int         mon = tm->tm_mon;
    int         mday = tm->tm_mday;
    int         hour = tm->tm_hour;
    int         min = tm->tm_min;
    int         sec = tm->tm_sec;
    bool        is_before = FALSE;
    bool        is_zero = TRUE;

    /*
     * The sign of year and month are guaranteed to match, since they are
     * stored internally as "month". But we'll need to check for is_before and
     * is_zero when determining the signs of day and hour/minute/seconds
     * fields.
     */
    switch (style)
    {
            /* SQL Standard interval format */
        case INTSTYLE_SQL_STANDARD:
            {
                bool        has_negative = year < 0 || mon < 0 ||
                mday < 0 || hour < 0 ||
                min < 0 || sec < 0 || fsec < 0;
                bool        has_positive = year > 0 || mon > 0 ||
                mday > 0 || hour > 0 ||
                min > 0 || sec > 0 || fsec > 0;
                bool        has_year_month = year != 0 || mon != 0;
                bool        has_day_time = mday != 0 || hour != 0 ||
                min != 0 || sec != 0 || fsec != 0;
                bool        has_day = mday != 0;
                bool        sql_standard_value = !(has_negative && has_positive) &&
                !(has_year_month && has_day_time);

                /*
                 * SQL Standard wants only 1 "<sign>" preceding the whole
                 * interval ... but can't do that if mixed signs.
                 */
                if (has_negative && sql_standard_value)
                {
                    *cp++ = '-';
                    year = -year;
                    mon = -mon;
                    mday = -mday;
                    hour = -hour;
                    min = -min;
                    sec = -sec;
                    fsec = -fsec;
                }

                if (!has_negative && !has_positive)
                {
                    sprintf(cp, "0");
                }
                else if (!sql_standard_value)
                {
                    /*
                     * For non sql-standard interval values, force outputting
                     * the signs to avoid ambiguities with intervals with
                     * mixed sign components.
                     */
                    char        year_sign = (year < 0 || mon < 0) ? '-' : '+';
                    char        day_sign = (mday < 0) ? '-' : '+';
                    char        sec_sign = (hour < 0 || min < 0 ||
                                            sec < 0 || fsec < 0) ? '-' : '+';

                    sprintf(cp, "%c%d-%d %c%d %c%d:%02d:",
                            year_sign, abs(year), abs(mon),
                            day_sign, abs(mday),
                            sec_sign, abs(hour), abs(min));
                    cp += strlen(cp);
                    AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
                }
                else if (has_year_month)
                {
                    sprintf(cp, "%d-%d", year, mon);
                }
                else if (has_day)
                {
                    sprintf(cp, "%d %d:%02d:", mday, hour, min);
                    cp += strlen(cp);
                    AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
                }
                else
                {
                    sprintf(cp, "%d:%02d:", hour, min);
                    cp += strlen(cp);
                    AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
                }
            }
            break;

            /* ISO 8601 "time-intervals by duration only" */
        case INTSTYLE_ISO_8601:
            /* special-case zero to avoid printing nothing */
            if (year == 0 && mon == 0 && mday == 0 &&
                hour == 0 && min == 0 && sec == 0 && fsec == 0)
            {
                sprintf(cp, "PT0S");
                break;
            }
            *cp++ = 'P';
            cp = AddISO8601IntPart(cp, year, 'Y');
            cp = AddISO8601IntPart(cp, mon, 'M');
            cp = AddISO8601IntPart(cp, mday, 'D');
            if (hour != 0 || min != 0 || sec != 0 || fsec != 0)
                *cp++ = 'T';
            cp = AddISO8601IntPart(cp, hour, 'H');
            cp = AddISO8601IntPart(cp, min, 'M');
            if (sec != 0 || fsec != 0)
            {
                if (sec < 0 || fsec < 0)
                    *cp++ = '-';
                AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
                cp += strlen(cp);
                *cp++ = 'S';
                *cp = '\0';
            }
            break;

            /* Compatible with postgresql < 8.4 when DateStyle = 'iso' */
        case INTSTYLE_POSTGRES:
            cp = AddPostgresIntPart(cp, year, "year", &is_zero, &is_before);
            cp = AddPostgresIntPart(cp, mon, "mon", &is_zero, &is_before);
            cp = AddPostgresIntPart(cp, mday, "day", &is_zero, &is_before);
            if (is_zero || hour != 0 || min != 0 || sec != 0 || fsec != 0)
            {
                bool        minus = (hour < 0 || min < 0 || sec < 0 || fsec < 0);

                sprintf(cp, "%s%s%02d:%02d:",
                        is_zero ? "" : " ",
                        (minus ? "-" : (is_before ? "+" : "")),
                        abs(hour), abs(min));
                cp += strlen(cp);
                AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
            }
            break;

            /* Compatible with postgresql < 8.4 when DateStyle != 'iso' */
        case INTSTYLE_POSTGRES_VERBOSE:
        default:
            strcpy(cp, "@");
            cp++;
            cp = AddVerboseIntPart(cp, year, "year", &is_zero, &is_before);
            cp = AddVerboseIntPart(cp, mon, "mon", &is_zero, &is_before);
            cp = AddVerboseIntPart(cp, mday, "day", &is_zero, &is_before);
            cp = AddVerboseIntPart(cp, hour, "hour", &is_zero, &is_before);
            cp = AddVerboseIntPart(cp, min, "min", &is_zero, &is_before);
            if (sec != 0 || fsec != 0)
            {
                *cp++ = ' ';
                if (sec < 0 || (sec == 0 && fsec < 0))
                {
                    if (is_zero)
                        is_before = TRUE;
                    else if (!is_before)
                        *cp++ = '-';
                }
                else if (is_before)
                    *cp++ = '-';
                AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
                cp += strlen(cp);
                sprintf(cp, " sec%s",
                        (abs(sec) != 1 || fsec != 0) ? "s" : "");
                is_zero = FALSE;
            }
            /* identically zero? then put in a unitless zero... */
            if (is_zero)
                strcat(cp, " 0");
            if (is_before)
                strcat(cp, " ago");
            break;
    }

    return 0;
}   /* EncodeInterval() */

static int interval2tm ( interval  span,
struct tm tm,
fsec_t fsec 
) [static]

Definition at line 987 of file interval.c.

References interval::month, SECS_PER_DAY, SECS_PER_HOUR, SECS_PER_MINUTE, interval::time, TMODULO, and TSROUND.

{
#ifdef HAVE_INT64_TIMESTAMP
    int64       time;
#else
    double      time;
#endif

    if (span.month != 0)
    {
        tm->tm_year = span.month / MONTHS_PER_YEAR;
        tm->tm_mon = span.month % MONTHS_PER_YEAR;

    }
    else
    {
        tm->tm_year = 0;
        tm->tm_mon = 0;
    }

    time = span.time;

#ifdef HAVE_INT64_TIMESTAMP
    tm->tm_mday = time / USECS_PER_DAY;
    time -= tm->tm_mday * USECS_PER_DAY;
    tm->tm_hour = time / USECS_PER_HOUR;
    time -= tm->tm_hour * USECS_PER_HOUR;
    tm->tm_min = time / USECS_PER_MINUTE;
    time -= tm->tm_min * USECS_PER_MINUTE;
    tm->tm_sec = time / USECS_PER_SEC;
    *fsec = time - (tm->tm_sec * USECS_PER_SEC);
#else
recalc:
    TMODULO(time, tm->tm_mday, (double) SECS_PER_DAY);
    TMODULO(time, tm->tm_hour, (double) SECS_PER_HOUR);
    TMODULO(time, tm->tm_min, (double) SECS_PER_MINUTE);
    TMODULO(time, tm->tm_sec, 1.0);
    time = TSROUND(time);
    /* roundoff may need to propagate to higher-order fields */
    if (time >= 1.0)
    {
        time = ceil(span.time);
        goto recalc;
    }
    *fsec = time;
#endif

    return 0;
}   /* interval2tm() */

static int ISO8601IntegerWidth ( char *  fieldstart  )  [static]

Definition at line 97 of file interval.c.

Referenced by DecodeISO8601Interval().

{
    /* We might have had a leading '-' */
    if (*fieldstart == '-')
        fieldstart++;
    return strspn(fieldstart, "0123456789");
}

static int ParseISO8601Number ( char *  str,
char **  endptr,
int *  ipart,
double *  fpart 
) [static]

Definition at line 72 of file interval.c.

References val.

Referenced by DecodeISO8601Interval().

{
    double      val;

    if (!(isdigit((unsigned char) *str) || *str == '-' || *str == '.'))
        return DTERR_BAD_FORMAT;
    errno = 0;
    val = strtod(str, endptr);
    /* did we not see anything that looks like a double? */
    if (*endptr == str || errno != 0)
        return DTERR_BAD_FORMAT;
    /* watch out for overflow */
    if (val < INT_MIN || val > INT_MAX)
        return DTERR_FIELD_OVERFLOW;
    /* be very sure we truncate towards zero (cf dtrunc()) */
    if (val >= 0)
        *ipart = (int) floor(val);
    else
        *ipart = (int) -floor(-val);
    *fpart = val - *ipart;
    return 0;
}

int PGTYPESinterval_copy ( interval intvlsrc,
interval intvldest 
)

Definition at line 1156 of file interval.c.

References interval::month, and interval::time.

Referenced by ecpg_get_data(), and main().

{
    intvldest->time = intvlsrc->time;
    intvldest->month = intvlsrc->month;

    return 0;
}

void PGTYPESinterval_free ( interval intvl  ) 

Definition at line 1067 of file interval.c.

References free.

Referenced by main().

{
    free(intvl);
}

interval* PGTYPESinterval_from_asc ( char *  str,
char **  endptr 
)

Definition at line 1073 of file interval.c.

References DecodeInterval(), DecodeISO8601Interval(), DTK_DELTA, free, MAXDATELEN, ParseDateTime(), pgtypes_alloc(), tm, tm2interval(), and pg_tm::tm_year.

Referenced by ecpg_get_data(), and main().

{
    interval   *result = NULL;
    fsec_t      fsec;
    struct tm   tt,
               *tm = &tt;
    int         dtype;
    int         nf;
    char       *field[MAXDATEFIELDS];
    int         ftype[MAXDATEFIELDS];
    char        lowstr[MAXDATELEN + MAXDATEFIELDS];
    char       *realptr;
    char      **ptr = (endptr != NULL) ? endptr : &realptr;

    tm->tm_year = 0;
    tm->tm_mon = 0;
    tm->tm_mday = 0;
    tm->tm_hour = 0;
    tm->tm_min = 0;
    tm->tm_sec = 0;
    fsec = 0;

    if (strlen(str) >= sizeof(lowstr))
    {
        errno = PGTYPES_INTVL_BAD_INTERVAL;
        return NULL;
    }

    if (ParseDateTime(str, lowstr, field, ftype, &nf, ptr) != 0 ||
        (DecodeInterval(field, ftype, nf, &dtype, tm, &fsec) != 0 &&
         DecodeISO8601Interval(str, &dtype, tm, &fsec) != 0))
    {
        errno = PGTYPES_INTVL_BAD_INTERVAL;
        return NULL;
    }

    result = (interval *) pgtypes_alloc(sizeof(interval));
    if (!result)
        return NULL;

    if (dtype != DTK_DELTA)
    {
        errno = PGTYPES_INTVL_BAD_INTERVAL;
        free(result);
        return NULL;
    }

    if (tm2interval(tm, fsec, result) != 0)
    {
        errno = PGTYPES_INTVL_BAD_INTERVAL;
        free(result);
        return NULL;
    }

    errno = 0;
    return result;
}

interval* PGTYPESinterval_new ( void   ) 

Definition at line 1057 of file interval.c.

References pgtypes_alloc().

Referenced by main().

{
    interval   *result;

    result = (interval *) pgtypes_alloc(sizeof(interval));
    /* result can be NULL if we run out of memory */
    return result;
}

char* PGTYPESinterval_to_asc ( interval span  ) 

Definition at line 1132 of file interval.c.

References buf, EncodeInterval(), interval2tm(), IntervalStyle, MAXDATELEN, pgtypes_strdup(), and tm.

Referenced by ecpg_store_input(), intoasc(), and main().

{
    struct tm   tt,
               *tm = &tt;
    fsec_t      fsec;
    char        buf[MAXDATELEN + 1];
    int         IntervalStyle = INTSTYLE_POSTGRES_VERBOSE;

    if (interval2tm(*span, tm, &fsec) != 0)
    {
        errno = PGTYPES_INTVL_BAD_INTERVAL;
        return NULL;
    }

    if (EncodeInterval(tm, fsec, IntervalStyle, buf) != 0)
    {
        errno = PGTYPES_INTVL_BAD_INTERVAL;
        return NULL;
    }

    return pgtypes_strdup(buf);
}

static int strtoi ( const char *  nptr,
char **  endptr,
int  base 
) [static]

Definition at line 19 of file interval.c.

References val.

Referenced by DecodeInterval().

{
    long        val;

    val = strtol(nptr, endptr, base);
#ifdef HAVE_LONG_INT_64
    if (val != (long) ((int32) val))
        errno = ERANGE;
#endif
    return (int) val;
}

static int tm2interval ( struct tm tm,
fsec_t  fsec,
interval span 
) [static]

Definition at line 1038 of file interval.c.

References HOURS_PER_DAY, INT64CONST, interval::month, MONTHS_PER_YEAR, SECS_PER_MINUTE, and interval::time.

{
    span->month = tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon;
#ifdef HAVE_INT64_TIMESTAMP
    span->time = (((((((tm->tm_mday * INT64CONST(24)) +
                       tm->tm_hour) * INT64CONST(60)) +
                     tm->tm_min) * INT64CONST(60)) +
                   tm->tm_sec) * USECS_PER_SEC) + fsec;
#else
    span->time = (((((tm->tm_mday * (double) HOURS_PER_DAY) +
                     tm->tm_hour) * (double) MINS_PER_HOUR) +
                   tm->tm_min) * (double) SECS_PER_MINUTE) +
        tm->tm_sec + fsec;
#endif

    return 0;
}   /* tm2interval() */