diff options
Diffstat (limited to 'src/backend/utils/adt/datetime.c')
-rw-r--r-- | src/backend/utils/adt/datetime.c | 105 |
1 files changed, 77 insertions, 28 deletions
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c index 462f2ed7a8a..4c12c4d6630 100644 --- a/src/backend/utils/adt/datetime.c +++ b/src/backend/utils/adt/datetime.c @@ -668,19 +668,50 @@ AdjustYears(int64 val, int scale, } -/* Fetch a fractional-second value with suitable error checking */ +/* + * Parse the fractional part of a number (decimal point and optional digits, + * followed by end of string). Returns the fractional value into *frac. + * + * Returns 0 if successful, DTERR code if bogus input detected. + */ +static int +ParseFraction(char *cp, double *frac) +{ + /* Caller should always pass the start of the fraction part */ + Assert(*cp == '.'); + + /* + * We want to allow just "." with no digits, but some versions of strtod + * will report EINVAL for that, so special-case it. + */ + if (cp[1] == '\0') + { + *frac = 0; + } + else + { + errno = 0; + *frac = strtod(cp, &cp); + /* check for parse failure */ + if (*cp != '\0' || errno != 0) + return DTERR_BAD_FORMAT; + } + return 0; +} + +/* + * Fetch a fractional-second value with suitable error checking. + * Same as ParseFraction except we convert the result to integer microseconds. + */ static int ParseFractionalSecond(char *cp, fsec_t *fsec) { double frac; + int dterr; - /* Caller should always pass the start of the fraction part */ - Assert(*cp == '.'); - errno = 0; - frac = strtod(cp, &cp); - /* check for parse failure */ - if (*cp != '\0' || errno != 0) - return DTERR_BAD_FORMAT; + dterr = ParseFraction(cp, &frac); + if (dterr) + return dterr; *fsec = rint(frac * 1000000); return 0; } @@ -1248,10 +1279,9 @@ DecodeDateTime(char **field, int *ftype, int nf, { double time; - errno = 0; - time = strtod(cp, &cp); - if (*cp != '\0' || errno != 0) - return DTERR_BAD_FORMAT; + dterr = ParseFraction(cp, &time); + if (dterr) + return dterr; time *= USECS_PER_DAY; dt2time(time, &tm->tm_hour, &tm->tm_min, @@ -2146,10 +2176,9 @@ DecodeTimeOnly(char **field, int *ftype, int nf, { double time; - errno = 0; - time = strtod(cp, &cp); - if (*cp != '\0' || errno != 0) - return DTERR_BAD_FORMAT; + dterr = ParseFraction(cp, &time); + if (dterr) + return dterr; time *= USECS_PER_DAY; dt2time(time, &tm->tm_hour, &tm->tm_min, @@ -3035,13 +3064,21 @@ DecodeNumberField(int len, char *str, int fmask, * Can we use ParseFractionalSecond here? Not clear whether trailing * junk should be rejected ... */ - double frac; + if (cp[1] == '\0') + { + /* avoid assuming that strtod will accept "." */ + *fsec = 0; + } + else + { + double frac; - errno = 0; - frac = strtod(cp, NULL); - if (errno != 0) - return DTERR_BAD_FORMAT; - *fsec = rint(frac * 1000000); + errno = 0; + frac = strtod(cp, NULL); + if (errno != 0) + return DTERR_BAD_FORMAT; + *fsec = rint(frac * 1000000); + } /* Now truncate off the fraction for further processing */ *cp = '\0'; len = strlen(str); @@ -3467,11 +3504,9 @@ DecodeInterval(char **field, int *ftype, int nf, int range, } else if (*cp == '.') { - errno = 0; - fval = strtod(cp, &cp); - if (*cp != '\0' || errno != 0) - return DTERR_BAD_FORMAT; - + dterr = ParseFraction(cp, &fval); + if (dterr) + return dterr; if (*field[i] == '-') fval = -fval; } @@ -3650,6 +3685,7 @@ DecodeInterval(char **field, int *ftype, int nf, int range, * Helper functions to avoid duplicated code in DecodeISO8601Interval. * * Parse a decimal value and break it into integer and fractional parts. + * Set *endptr to end+1 of the parsed substring. * Returns 0 or DTERR code. */ static int @@ -3676,7 +3712,20 @@ ParseISO8601Number(char *str, char **endptr, int64 *ipart, double *fpart) /* Parse fractional part if there is any */ if (**endptr == '.') - *fpart = strtod(*endptr, endptr) * sign; + { + /* + * As in ParseFraction, some versions of strtod insist on seeing some + * digits after '.', but some don't. We want to allow zero digits + * after '.' as long as there were some before it. + */ + if (isdigit((unsigned char) *(*endptr + 1))) + *fpart = strtod(*endptr, endptr) * sign; + else + { + (*endptr)++; /* advance over '.' */ + str++; /* so next test will fail if no digits */ + } + } /* did we not see anything that looks like a number? */ if (*endptr == str || errno != 0) |