Improve speed of timestamp/time/date output functions.

It seems that sprintf(), at least in glibc's version, is unreasonably slow
compared to hand-rolled code for printing integers.  Replacing most uses of
sprintf() in the datetime.c output functions with special-purpose code
turns out to give more than a 2X speedup in COPY of a table with a single
timestamp column; which is pretty impressive considering all the other
logic in that code path.

David Rowley and Andres Freund, reviewed by Peter Geoghegan and myself
This commit is contained in:
Tom Lane 2016-02-06 23:11:28 -05:00
parent b921aeb167
commit aa2387e2fd
3 changed files with 424 additions and 128 deletions

View File

@ -43,8 +43,12 @@ static int DecodeTime(char *str, int fmask, int range,
static const datetkn *datebsearch(const char *key, const datetkn *base, int nel); static const datetkn *datebsearch(const char *key, const datetkn *base, int nel);
static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits, static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
struct pg_tm * tm); struct pg_tm * tm);
static void TrimTrailingZeros(char *str);
static void AppendSeconds(char *cp, int sec, fsec_t fsec, #ifndef HAVE_INT64_TIMESTAMP
static char *TrimTrailingZeros(char *str);
#endif /* HAVE_INT64_TIMESTAMP */
static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
int precision, bool fillzeros); int precision, bool fillzeros);
static void AdjustFractSeconds(double frac, struct pg_tm * tm, fsec_t *fsec, static void AdjustFractSeconds(double frac, struct pg_tm * tm, fsec_t *fsec,
int scale); int scale);
@ -398,57 +402,121 @@ GetCurrentTimeUsec(struct pg_tm * tm, fsec_t *fsec, int *tzp)
/* TrimTrailingZeros() /* TrimTrailingZeros()
* ... resulting from printing numbers with full precision. * ... resulting from printing numbers with full precision.
* *
* Returns a pointer to the new end of string. No NUL terminator is put
* there; callers are responsible for NUL terminating str themselves.
*
* Before Postgres 8.4, this always left at least 2 fractional digits, * Before Postgres 8.4, this always left at least 2 fractional digits,
* but conversations on the lists suggest this isn't desired * but conversations on the lists suggest this isn't desired
* since showing '0.10' is misleading with values of precision(1). * since showing '0.10' is misleading with values of precision(1).
*/ */
static void #ifndef HAVE_INT64_TIMESTAMP
static char *
TrimTrailingZeros(char *str) TrimTrailingZeros(char *str)
{ {
int len = strlen(str); int len = strlen(str);
while (len > 1 && *(str + len - 1) == '0' && *(str + len - 2) != '.') while (len > 1 && *(str + len - 1) == '0' && *(str + len - 2) != '.')
{
len--; len--;
*(str + len) = '\0'; return str + len;
}
} }
#endif /* HAVE_INT64_TIMESTAMP */
/* /*
* Append sections and fractional seconds (if any) at *cp. * Append seconds and fractional seconds (if any) at *cp.
*
* precision is the max number of fraction digits, fillzeros says to * precision is the max number of fraction digits, fillzeros says to
* pad to two integral-seconds digits. * pad to two integral-seconds digits.
*
* Returns a pointer to the new end of string. No NUL terminator is put
* there; callers are responsible for NUL terminating str themselves.
*
* Note that any sign is stripped from the input seconds values. * Note that any sign is stripped from the input seconds values.
*/ */
static void static char *
AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros) AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros)
{ {
Assert(precision >= 0);
#ifdef HAVE_INT64_TIMESTAMP
/* fsec_t is just an int32 */
if (fillzeros)
cp = pg_ltostr_zeropad(cp, Abs(sec), 2);
else
cp = pg_ltostr(cp, Abs(sec));
if (fsec != 0)
{
int32 value = Abs(fsec);
char *end = &cp[precision + 1];
bool gotnonzero = false;
*cp++ = '.';
/*
* Append the fractional seconds part. Note that we don't want any
* trailing zeros here, so since we're building the number in reverse
* we'll skip appending zeros until we've output a non-zero digit.
*/
while (precision--)
{
int32 oldval = value;
int32 remainder;
value /= 10;
remainder = oldval - value * 10;
/* check if we got a non-zero */
if (remainder)
gotnonzero = true;
if (gotnonzero)
cp[precision] = '0' + remainder;
else
end = &cp[precision];
}
/*
* If we still have a non-zero value then precision must have not been
* enough to print the number. We punt the problem to pg_ltostr(),
* which will generate a correct answer in the minimum valid width.
*/
if (value)
return pg_ltostr(cp, Abs(fsec));
return end;
}
else
return cp;
#else
/* fsec_t is a double */
if (fsec == 0) if (fsec == 0)
{ {
if (fillzeros) if (fillzeros)
sprintf(cp, "%02d", abs(sec)); return pg_ltostr_zeropad(cp, Abs(sec), 2);
else else
sprintf(cp, "%d", abs(sec)); return pg_ltostr(cp, Abs(sec));
} }
else 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) if (fillzeros)
sprintf(cp, "%0*.*f", precision + 3, precision, fabs(sec + fsec)); sprintf(cp, "%0*.*f", precision + 3, precision, fabs(sec + fsec));
else else
sprintf(cp, "%.*f", precision, fabs(sec + fsec)); sprintf(cp, "%.*f", precision, fabs(sec + fsec));
#endif return TrimTrailingZeros(cp);
TrimTrailingZeros(cp);
} }
#endif /* HAVE_INT64_TIMESTAMP */
} }
/* Variant of above that's specialized to timestamp case */
static void /*
* Variant of above that's specialized to timestamp case.
*
* Returns a pointer to the new end of string. No NUL terminator is put
* there; callers are responsible for NUL terminating str themselves.
*/
static char *
AppendTimestampSeconds(char *cp, struct pg_tm * tm, fsec_t fsec) AppendTimestampSeconds(char *cp, struct pg_tm * tm, fsec_t fsec)
{ {
/* /*
@ -459,7 +527,7 @@ AppendTimestampSeconds(char *cp, struct pg_tm * tm, fsec_t fsec)
if (tm->tm_year <= 0) if (tm->tm_year <= 0)
fsec = 0; fsec = 0;
#endif #endif
AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true); return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
} }
/* /*
@ -3831,9 +3899,12 @@ datebsearch(const char *key, const datetkn *base, int nel)
} }
/* EncodeTimezone() /* EncodeTimezone()
* Append representation of a numeric timezone offset to str. * Copies representation of a numeric timezone offset to str.
*
* Returns a pointer to the new end of string. No NUL terminator is put
* there; callers are responsible for NUL terminating str themselves.
*/ */
static void static char *
EncodeTimezone(char *str, int tz, int style) EncodeTimezone(char *str, int tz, int style)
{ {
int hour, int hour,
@ -3846,16 +3917,26 @@ EncodeTimezone(char *str, int tz, int style)
hour = min / MINS_PER_HOUR; hour = min / MINS_PER_HOUR;
min -= hour * MINS_PER_HOUR; min -= hour * MINS_PER_HOUR;
str += strlen(str);
/* TZ is negated compared to sign we wish to display ... */ /* TZ is negated compared to sign we wish to display ... */
*str++ = (tz <= 0 ? '+' : '-'); *str++ = (tz <= 0 ? '+' : '-');
if (sec != 0) if (sec != 0)
sprintf(str, "%02d:%02d:%02d", hour, min, sec); {
str = pg_ltostr_zeropad(str, hour, 2);
*str++ = ':';
str = pg_ltostr_zeropad(str, min, 2);
*str++ = ':';
str = pg_ltostr_zeropad(str, sec, 2);
}
else if (min != 0 || style == USE_XSD_DATES) else if (min != 0 || style == USE_XSD_DATES)
sprintf(str, "%02d:%02d", hour, min); {
str = pg_ltostr_zeropad(str, hour, 2);
*str++ = ':';
str = pg_ltostr_zeropad(str, min, 2);
}
else else
sprintf(str, "%02d", hour); str = pg_ltostr_zeropad(str, hour, 2);
return str;
} }
/* EncodeDateOnly() /* EncodeDateOnly()
@ -3871,48 +3952,70 @@ EncodeDateOnly(struct pg_tm * tm, int style, char *str)
case USE_ISO_DATES: case USE_ISO_DATES:
case USE_XSD_DATES: case USE_XSD_DATES:
/* compatible with ISO date formats */ /* compatible with ISO date formats */
if (tm->tm_year > 0) str = pg_ltostr_zeropad(str,
sprintf(str, "%04d-%02d-%02d", (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
tm->tm_year, tm->tm_mon, tm->tm_mday); *str++ = '-';
else str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
sprintf(str, "%04d-%02d-%02d %s", *str++ = '-';
-(tm->tm_year - 1), tm->tm_mon, tm->tm_mday, "BC"); str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
break; break;
case USE_SQL_DATES: case USE_SQL_DATES:
/* compatible with Oracle/Ingres date formats */ /* compatible with Oracle/Ingres date formats */
if (DateOrder == DATEORDER_DMY) if (DateOrder == DATEORDER_DMY)
sprintf(str, "%02d/%02d", tm->tm_mday, tm->tm_mon); {
str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
*str++ = '/';
str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
}
else else
sprintf(str, "%02d/%02d", tm->tm_mon, tm->tm_mday); {
if (tm->tm_year > 0) str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
sprintf(str + 5, "/%04d", tm->tm_year); *str++ = '/';
else str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
sprintf(str + 5, "/%04d %s", -(tm->tm_year - 1), "BC"); }
*str++ = '/';
str = pg_ltostr_zeropad(str,
(tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
break; break;
case USE_GERMAN_DATES: case USE_GERMAN_DATES:
/* German-style date format */ /* German-style date format */
sprintf(str, "%02d.%02d", tm->tm_mday, tm->tm_mon); str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
if (tm->tm_year > 0) *str++ = '.';
sprintf(str + 5, ".%04d", tm->tm_year); str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
else *str++ = '.';
sprintf(str + 5, ".%04d %s", -(tm->tm_year - 1), "BC"); str = pg_ltostr_zeropad(str,
(tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
break; break;
case USE_POSTGRES_DATES: case USE_POSTGRES_DATES:
default: default:
/* traditional date-only style for Postgres */ /* traditional date-only style for Postgres */
if (DateOrder == DATEORDER_DMY) if (DateOrder == DATEORDER_DMY)
sprintf(str, "%02d-%02d", tm->tm_mday, tm->tm_mon); {
str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
*str++ = '-';
str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
}
else else
sprintf(str, "%02d-%02d", tm->tm_mon, tm->tm_mday); {
if (tm->tm_year > 0) str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
sprintf(str + 5, "-%04d", tm->tm_year); *str++ = '-';
else str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
sprintf(str + 5, "-%04d %s", -(tm->tm_year - 1), "BC"); }
*str++ = '-';
str = pg_ltostr_zeropad(str,
(tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
break; break;
} }
if (tm->tm_year <= 0)
{
memcpy(str, " BC", 3); /* Don't copy NUL */
str += 3;
}
*str = '\0';
} }
@ -3927,13 +4030,14 @@ EncodeDateOnly(struct pg_tm * tm, int style, char *str)
void void
EncodeTimeOnly(struct pg_tm * tm, fsec_t fsec, bool print_tz, int tz, int style, char *str) EncodeTimeOnly(struct pg_tm * tm, fsec_t fsec, bool print_tz, int tz, int style, char *str)
{ {
sprintf(str, "%02d:%02d:", tm->tm_hour, tm->tm_min); str = pg_ltostr_zeropad(str, tm->tm_hour, 2);
str += strlen(str); *str++ = ':';
str = pg_ltostr_zeropad(str, tm->tm_min, 2);
AppendSeconds(str, tm->tm_sec, fsec, MAX_TIME_PRECISION, true); *str++ = ':';
str = AppendSeconds(str, tm->tm_sec, fsec, MAX_TIME_PRECISION, true);
if (print_tz) if (print_tz)
EncodeTimezone(str, tz, style); str = EncodeTimezone(str, tz, style);
*str = '\0';
} }
@ -3971,106 +4075,129 @@ EncodeDateTime(struct pg_tm * tm, fsec_t fsec, bool print_tz, int tz, const char
case USE_ISO_DATES: case USE_ISO_DATES:
case USE_XSD_DATES: case USE_XSD_DATES:
/* Compatible with ISO-8601 date formats */ /* Compatible with ISO-8601 date formats */
str = pg_ltostr_zeropad(str,
if (style == USE_ISO_DATES) (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
sprintf(str, "%04d-%02d-%02d %02d:%02d:", *str++ = '-';
(tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min); *str++ = '-';
else str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
sprintf(str, "%04d-%02d-%02dT%02d:%02d:", *str++ = (style == USE_ISO_DATES) ? ' ' : 'T';
(tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), str = pg_ltostr_zeropad(str, tm->tm_hour, 2);
tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min); *str++ = ':';
str = pg_ltostr_zeropad(str, tm->tm_min, 2);
AppendTimestampSeconds(str + strlen(str), tm, fsec); *str++ = ':';
str = AppendTimestampSeconds(str, tm, fsec);
if (print_tz) if (print_tz)
EncodeTimezone(str, tz, style); str = EncodeTimezone(str, tz, style);
if (tm->tm_year <= 0)
sprintf(str + strlen(str), " BC");
break; break;
case USE_SQL_DATES: case USE_SQL_DATES:
/* Compatible with Oracle/Ingres date formats */ /* Compatible with Oracle/Ingres date formats */
if (DateOrder == DATEORDER_DMY) if (DateOrder == DATEORDER_DMY)
sprintf(str, "%02d/%02d", tm->tm_mday, tm->tm_mon); {
str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
*str++ = '/';
str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
}
else else
sprintf(str, "%02d/%02d", tm->tm_mon, tm->tm_mday); {
str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
sprintf(str + 5, "/%04d %02d:%02d:", *str++ = '/';
(tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
tm->tm_hour, tm->tm_min); }
*str++ = '/';
AppendTimestampSeconds(str + strlen(str), tm, fsec); str = pg_ltostr_zeropad(str,
(tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
*str++ = ' ';
str = pg_ltostr_zeropad(str, tm->tm_hour, 2);
*str++ = ':';
str = pg_ltostr_zeropad(str, tm->tm_min, 2);
*str++ = ':';
str = AppendTimestampSeconds(str, tm, fsec);
/* /*
* Note: the uses of %.*s in this function would be risky if the * Note: the uses of %.*s in this function would be risky if the
* timezone names ever contain non-ASCII characters. However, all * timezone names ever contain non-ASCII characters. However, all
* TZ abbreviations in the Olson database are plain ASCII. * TZ abbreviations in the IANA database are plain ASCII.
*/ */
if (print_tz) if (print_tz)
{ {
if (tzn) if (tzn)
sprintf(str + strlen(str), " %.*s", MAXTZLEN, tzn); {
sprintf(str, " %.*s", MAXTZLEN, tzn);
str += strlen(str);
}
else else
EncodeTimezone(str, tz, style); str = EncodeTimezone(str, tz, style);
} }
if (tm->tm_year <= 0)
sprintf(str + strlen(str), " BC");
break; break;
case USE_GERMAN_DATES: case USE_GERMAN_DATES:
/* German variant on European style */ /* German variant on European style */
str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
sprintf(str, "%02d.%02d", tm->tm_mday, tm->tm_mon); *str++ = '.';
str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
sprintf(str + 5, ".%04d %02d:%02d:", *str++ = '.';
(tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), str = pg_ltostr_zeropad(str,
tm->tm_hour, tm->tm_min); (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
*str++ = ' ';
AppendTimestampSeconds(str + strlen(str), tm, fsec); str = pg_ltostr_zeropad(str, tm->tm_hour, 2);
*str++ = ':';
str = pg_ltostr_zeropad(str, tm->tm_min, 2);
*str++ = ':';
str = AppendTimestampSeconds(str, tm, fsec);
if (print_tz) if (print_tz)
{ {
if (tzn) if (tzn)
sprintf(str + strlen(str), " %.*s", MAXTZLEN, tzn); {
sprintf(str, " %.*s", MAXTZLEN, tzn);
str += strlen(str);
}
else else
EncodeTimezone(str, tz, style); str = EncodeTimezone(str, tz, style);
} }
if (tm->tm_year <= 0)
sprintf(str + strlen(str), " BC");
break; break;
case USE_POSTGRES_DATES: case USE_POSTGRES_DATES:
default: default:
/* Backward-compatible with traditional Postgres abstime dates */ /* Backward-compatible with traditional Postgres abstime dates */
day = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday); day = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday);
tm->tm_wday = j2day(day); tm->tm_wday = j2day(day);
memcpy(str, days[tm->tm_wday], 3); memcpy(str, days[tm->tm_wday], 3);
strcpy(str + 3, " "); str += 3;
*str++ = ' ';
if (DateOrder == DATEORDER_DMY) if (DateOrder == DATEORDER_DMY)
sprintf(str + 4, "%02d %3s", tm->tm_mday, months[tm->tm_mon - 1]); {
str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
*str++ = ' ';
memcpy(str, months[tm->tm_mon - 1], 3);
str += 3;
}
else else
sprintf(str + 4, "%3s %02d", months[tm->tm_mon - 1], tm->tm_mday); {
memcpy(str, months[tm->tm_mon - 1], 3);
sprintf(str + 10, " %02d:%02d:", tm->tm_hour, tm->tm_min); str += 3;
*str++ = ' ';
AppendTimestampSeconds(str + strlen(str), tm, fsec); str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
}
sprintf(str + strlen(str), " %04d", *str++ = ' ';
(tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1)); str = pg_ltostr_zeropad(str, tm->tm_hour, 2);
*str++ = ':';
str = pg_ltostr_zeropad(str, tm->tm_min, 2);
*str++ = ':';
str = AppendTimestampSeconds(str, tm, fsec);
*str++ = ' ';
str = pg_ltostr_zeropad(str,
(tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
if (print_tz) if (print_tz)
{ {
if (tzn) if (tzn)
sprintf(str + strlen(str), " %.*s", MAXTZLEN, tzn); {
sprintf(str, " %.*s", MAXTZLEN, tzn);
str += strlen(str);
}
else else
{ {
/* /*
@ -4079,15 +4206,19 @@ EncodeDateTime(struct pg_tm * tm, fsec_t fsec, bool print_tz, int tz, const char
* avoid formatting something which would be rejected by * avoid formatting something which would be rejected by
* the date/time parser later. - thomas 2001-10-19 * the date/time parser later. - thomas 2001-10-19
*/ */
sprintf(str + strlen(str), " "); *str++ = ' ';
EncodeTimezone(str, tz, style); str = EncodeTimezone(str, tz, style);
} }
} }
if (tm->tm_year <= 0)
sprintf(str + strlen(str), " BC");
break; break;
} }
if (tm->tm_year <= 0)
{
memcpy(str, " BC", 3); /* Don't copy NUL */
str += 3;
}
*str = '\0';
} }
@ -4242,7 +4373,8 @@ EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str)
day_sign, abs(mday), day_sign, abs(mday),
sec_sign, abs(hour), abs(min)); sec_sign, abs(hour), abs(min));
cp += strlen(cp); cp += strlen(cp);
AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true); cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
*cp = '\0';
} }
else if (has_year_month) else if (has_year_month)
{ {
@ -4252,13 +4384,15 @@ EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str)
{ {
sprintf(cp, "%d %d:%02d:", mday, hour, min); sprintf(cp, "%d %d:%02d:", mday, hour, min);
cp += strlen(cp); cp += strlen(cp);
AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true); cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
*cp = '\0';
} }
else else
{ {
sprintf(cp, "%d:%02d:", hour, min); sprintf(cp, "%d:%02d:", hour, min);
cp += strlen(cp); cp += strlen(cp);
AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true); cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
*cp = '\0';
} }
} }
break; break;
@ -4284,8 +4418,7 @@ EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str)
{ {
if (sec < 0 || fsec < 0) if (sec < 0 || fsec < 0)
*cp++ = '-'; *cp++ = '-';
AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false); cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
cp += strlen(cp);
*cp++ = 'S'; *cp++ = 'S';
*cp++ = '\0'; *cp++ = '\0';
} }
@ -4311,7 +4444,8 @@ EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str)
(minus ? "-" : (is_before ? "+" : "")), (minus ? "-" : (is_before ? "+" : "")),
abs(hour), abs(min)); abs(hour), abs(min));
cp += strlen(cp); cp += strlen(cp);
AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true); cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
*cp = '\0';
} }
break; break;
@ -4337,8 +4471,7 @@ EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str)
} }
else if (is_before) else if (is_before)
*cp++ = '-'; *cp++ = '-';
AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false); cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
cp += strlen(cp);
sprintf(cp, " sec%s", sprintf(cp, " sec%s",
(abs(sec) != 1 || fsec != 0) ? "s" : ""); (abs(sec) != 1 || fsec != 0) ? "s" : "");
is_zero = FALSE; is_zero = FALSE;

View File

@ -227,3 +227,164 @@ pg_lltoa(int64 value, char *a)
*a-- = swap; *a-- = swap;
} }
} }
/*
* pg_ltostr_zeropad
* Converts 'value' into a decimal string representation stored at 'str'.
* 'minwidth' specifies the minimum width of the result; any extra space
* is filled up by prefixing the number with zeros.
*
* Returns the ending address of the string result (the last character written
* plus 1). Note that no NUL terminator is written.
*
* The intended use-case for this function is to build strings that contain
* multiple individual numbers, for example:
*
* str = pg_ltostr_zeropad(str, hours, 2);
* *str++ = ':';
* str = pg_ltostr_zeropad(str, mins, 2);
* *str++ = ':';
* str = pg_ltostr_zeropad(str, secs, 2);
* *str = '\0';
*
* Note: Caller must ensure that 'str' points to enough memory to hold the
* result.
*/
char *
pg_ltostr_zeropad(char *str, int32 value, int32 minwidth)
{
char *start = str;
char *end = &str[minwidth];
int32 num = value;
Assert(minwidth > 0);
/*
* Handle negative numbers in a special way. We can't just write a '-'
* prefix and reverse the sign as that would overflow for INT32_MIN.
*/
if (num < 0)
{
*start++ = '-';
minwidth--;
/*
* Build the number starting at the last digit. Here remainder will
* be a negative number, so we must reverse the sign before adding '0'
* in order to get the correct ASCII digit.
*/
while (minwidth--)
{
int32 oldval = num;
int32 remainder;
num /= 10;
remainder = oldval - num * 10;
start[minwidth] = '0' - remainder;
}
}
else
{
/* Build the number starting at the last digit */
while (minwidth--)
{
int32 oldval = num;
int32 remainder;
num /= 10;
remainder = oldval - num * 10;
start[minwidth] = '0' + remainder;
}
}
/*
* If minwidth was not high enough to fit the number then num won't have
* been divided down to zero. We punt the problem to pg_ltostr(), which
* will generate a correct answer in the minimum valid width.
*/
if (num != 0)
return pg_ltostr(str, value);
/* Otherwise, return last output character + 1 */
return end;
}
/*
* pg_ltostr
* Converts 'value' into a decimal string representation stored at 'str'.
*
* Returns the ending address of the string result (the last character written
* plus 1). Note that no NUL terminator is written.
*
* The intended use-case for this function is to build strings that contain
* multiple individual numbers, for example:
*
* str = pg_ltostr(str, a);
* *str++ = ' ';
* str = pg_ltostr(str, b);
* *str = '\0';
*
* Note: Caller must ensure that 'str' points to enough memory to hold the
* result.
*/
char *
pg_ltostr(char *str, int32 value)
{
char *start;
char *end;
/*
* Handle negative numbers in a special way. We can't just write a '-'
* prefix and reverse the sign as that would overflow for INT32_MIN.
*/
if (value < 0)
{
*str++ = '-';
/* Mark the position we must reverse the string from. */
start = str;
/* Compute the result string backwards. */
do
{
int32 oldval = value;
int32 remainder;
value /= 10;
remainder = oldval - value * 10;
/* As above, we expect remainder to be negative. */
*str++ = '0' - remainder;
} while (value != 0);
}
else
{
/* Mark the position we must reverse the string from. */
start = str;
/* Compute the result string backwards. */
do
{
int32 oldval = value;
int32 remainder;
value /= 10;
remainder = oldval - value * 10;
*str++ = '0' + remainder;
} while (value != 0);
}
/* Remember the end+1 and back up 'str' to the last character. */
end = str--;
/* Reverse string. */
while (start < str)
{
char swap = *start;
*start++ = *str;
*str-- = swap;
}
return end;
}

View File

@ -290,6 +290,8 @@ extern int32 pg_atoi(const char *s, int size, int c);
extern void pg_itoa(int16 i, char *a); extern void pg_itoa(int16 i, char *a);
extern void pg_ltoa(int32 l, char *a); extern void pg_ltoa(int32 l, char *a);
extern void pg_lltoa(int64 ll, char *a); extern void pg_lltoa(int64 ll, char *a);
extern char *pg_ltostr_zeropad(char *str, int32 value, int32 minwidth);
extern char *pg_ltostr(char *str, int32 value);
/* /*
* Per-opclass comparison functions for new btrees. These are * Per-opclass comparison functions for new btrees. These are