}
-/* 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;
}
{
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,
{
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,
* 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);
}
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;
}
* 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
/* 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)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
- COPY_tm(tm, &tt);
- thisdate = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday);
- tm->tm_wday = (thisdate + 1) % 7;
- tm->tm_yday = thisdate - date2j(tm->tm_year, 1, 1) + 1;
+ /* calculate wday and yday, because timestamp2tm doesn't */
+ thisdate = date2j(tt.tm_year, tt.tm_mon, tt.tm_mday);
+ tt.tm_wday = (thisdate + 1) % 7;
+ tt.tm_yday = thisdate - date2j(tt.tm_year, 1, 1) + 1;
+
+ COPY_tm(tm, &tt);
if (!(res = datetime_to_char_body(&tmtc, fmt, false, PG_GET_COLLATION())))
PG_RETURN_NULL();
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
- COPY_tm(tm, &tt);
- thisdate = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday);
- tm->tm_wday = (thisdate + 1) % 7;
- tm->tm_yday = thisdate - date2j(tm->tm_year, 1, 1) + 1;
+ /* calculate wday and yday, because timestamp2tm doesn't */
+ thisdate = date2j(tt.tm_year, tt.tm_mon, tt.tm_mday);
+ tt.tm_wday = (thisdate + 1) % 7;
+ tt.tm_yday = thisdate - date2j(tt.tm_year, 1, 1) + 1;
+
+ COPY_tm(tm, &tt);
if (!(res = datetime_to_char_body(&tmtc, fmt, false, PG_GET_COLLATION())))
PG_RETURN_NULL();
2 years | 2 years 10 mons | 2 years 10 mons 15 days | 2 years 00:00:01 | 2 years 10 mons 00:00:01 | 2 years 10 mons 15 days 00:00:01 | 10:00:00 | 10:30:00
(1 row)
+-- Check handling of fractional fields in ISO8601 format.
+select interval 'P1Y0M3DT4H5M6S';
+ interval
+------------------------
+ 1 year 3 days 04:05:06
+(1 row)
+
+select interval 'P1.0Y0M3DT4H5M6S';
+ interval
+------------------------
+ 1 year 3 days 04:05:06
+(1 row)
+
+select interval 'P1.1Y0M3DT4H5M6S';
+ interval
+------------------------------
+ 1 year 1 mon 3 days 04:05:06
+(1 row)
+
+select interval 'P1.Y0M3DT4H5M6S';
+ interval
+------------------------
+ 1 year 3 days 04:05:06
+(1 row)
+
+select interval 'P.1Y0M3DT4H5M6S';
+ interval
+-----------------------
+ 1 mon 3 days 04:05:06
+(1 row)
+
+select interval 'P.Y0M3DT4H5M6S'; -- error
+ERROR: invalid input syntax for type interval: "P.Y0M3DT4H5M6S"
+LINE 1: select interval 'P.Y0M3DT4H5M6S';
+ ^
-- test a couple rounding cases that changed since 8.3 w/ HAVE_INT64_TIMESTAMP.
SET IntervalStyle to postgres_verbose;
select interval '-10 mons -3 days +03:55:06.70';