diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 926358e..d0bb930 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -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 int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
 		   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);
 static void AdjustFractSeconds(double frac, struct pg_tm * tm, fsec_t *fsec,
 				   int scale);
@@ -394,15 +398,16 @@ GetCurrentTimeUsec(struct pg_tm * tm, fsec_t *fsec, int *tzp)
 		*tzp = tz;
 }
 
-
+#ifndef HAVE_INT64_TIMESTAMP
 /* TrimTrailingZeros()
  * ... resulting from printing numbers with full precision.
  *
  * Before Postgres 8.4, this always left at least 2 fractional digits,
  * but conversations on the lists suggest this isn't desired
  * since showing '0.10' is misleading with values of precision(1).
+ * Returns a pointer to the new end of string.
  */
-static void
+static char *
 TrimTrailingZeros(char *str)
 {
 	int			len = strlen(str);
@@ -412,43 +417,100 @@ TrimTrailingZeros(char *str)
 		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
  * pad to two integral-seconds digits.
  * Note that any sign is stripped from the input seconds values.
+ * Note 'precision' must not be a negative number.
+ * Note callers should assume cp will not be NUL terminated.
  */
-static void
+static char *
 AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros)
 {
+#ifdef HAVE_INT64_TIMESTAMP
+
+	if (fillzeros)
+		cp = pg_ltostr_zeropad(cp, abs(sec), 2);
+	else
+		cp = pg_ltostr(cp, abs(sec));
+
+	if (fsec != 0)
+	{
+		int			value = (int) 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 any zeros, unless we've seen a non-zero.
+		 */
+		while (precision--)
+		{
+			int		remainder;
+			int		oldval = value;
+
+			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 have a non-zero value then precision must have not been enough
+		 * to print the number, we'd better have another go. There won't be any
+		 * zero padding, so we can just use pg_ltostr().
+		 */
+		if (value > 0)
+			return pg_ltostr(cp, (int) Abs(fsec));
+
+		return end;
+	}
+	else
+		return cp;
+#else
+
 	if (fsec == 0)
 	{
 		if (fillzeros)
-			sprintf(cp, "%02d", abs(sec));
+			return pg_ltostr_zeropad(cp, abs(sec), 2);
 		else
-			sprintf(cp, "%d", abs(sec));
+			return pg_ltostr(cp, 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);
+
+		return 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
+ * Note callers should assume cp will not be NUL terminated.
+ */
+static char *
 AppendTimestampSeconds(char *cp, struct pg_tm * tm, fsec_t fsec)
 {
 	/*
@@ -459,7 +521,7 @@ AppendTimestampSeconds(char *cp, struct pg_tm * tm, fsec_t fsec)
 	if (tm->tm_year <= 0)
 		fsec = 0;
 #endif
-	AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
+	return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
 }
 
 /*
@@ -3831,9 +3893,13 @@ datebsearch(const char *key, const datetkn *base, int nel)
 }
 
 /* EncodeTimezone()
- *		Append representation of a numeric timezone offset to str.
+ *		Copies representation of a numeric timezone offset into str.
+ *		Returns a pointer pointing to where the NUL character should be set.
+ *
+ * Note str is not NUL terminated, callers are responsible for NUL terminating
+ * str themselves.
  */
-static void
+static char *
 EncodeTimezone(char *str, int tz, int style)
 {
 	int			hour,
@@ -3846,16 +3912,26 @@ EncodeTimezone(char *str, int tz, int style)
 	hour = min / MINS_PER_HOUR;
 	min -= hour * MINS_PER_HOUR;
 
-	str += strlen(str);
 	/* TZ is negated compared to sign we wish to display ... */
 	*str++ = (tz <= 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)
-		sprintf(str, "%02d:%02d", hour, min);
+	{
+		str = pg_ltostr_zeropad(str, hour, 2);
+		*str++ = ':';
+		str = pg_ltostr_zeropad(str, min, 2);
+	}
 	else
-		sprintf(str, "%02d", hour);
+		str = pg_ltostr_zeropad(str, hour, 2);
+	return str;
 }
 
 /* EncodeDateOnly()
@@ -3871,46 +3947,90 @@ EncodeDateOnly(struct pg_tm * tm, int style, char *str)
 		case USE_ISO_DATES:
 		case USE_XSD_DATES:
 			/* compatible with ISO date formats */
-			if (tm->tm_year > 0)
-				sprintf(str, "%04d-%02d-%02d",
-						tm->tm_year, tm->tm_mon, tm->tm_mday);
-			else
-				sprintf(str, "%04d-%02d-%02d %s",
-						-(tm->tm_year - 1), tm->tm_mon, tm->tm_mday, "BC");
+			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_mon, 2);
+			*str++ = '-';
+			str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+
+			if (tm->tm_year <= 0)
+			{
+				memcpy(str, " BC", 3); /* Don't copy NUL */
+				str += 3;
+			}
+			*str = '\0';
 			break;
 
 		case USE_SQL_DATES:
 			/* compatible with Oracle/Ingres date formats */
 			if (DateOrder == DATEORDER_DMY)
-				sprintf(str, "%02d/%02d", tm->tm_mday, tm->tm_mon);
-			else
-				sprintf(str, "%02d/%02d", tm->tm_mon, tm->tm_mday);
-			if (tm->tm_year > 0)
-				sprintf(str + 5, "/%04d", tm->tm_year);
+			{
+				str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+				*str++ = '/';
+				str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
+			}
 			else
-				sprintf(str + 5, "/%04d %s", -(tm->tm_year - 1), "BC");
+			{
+				str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
+				*str++ = '/';
+				str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+			}
+
+			*str++ = '/';
+			str = pg_ltostr_zeropad(str,
+				(tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
+
+			if (tm->tm_year <= 0)
+			{
+				memcpy(str, " BC", 3); /* Don't copy NUL */
+				str += 3;
+			}
+			*str = '\0';
 			break;
 
 		case USE_GERMAN_DATES:
 			/* German-style date format */
-			sprintf(str, "%02d.%02d", tm->tm_mday, tm->tm_mon);
-			if (tm->tm_year > 0)
-				sprintf(str + 5, ".%04d", tm->tm_year);
-			else
-				sprintf(str + 5, ".%04d %s", -(tm->tm_year - 1), "BC");
+			str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+			*str++ = '.';
+			str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
+			*str++ = '.';
+			str = pg_ltostr_zeropad(str,
+				(tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
+
+			if (tm->tm_year <= 0)
+			{
+				memcpy(str, " BC", 3); /* Don't copy NUL */
+				str += 3;
+			}
+			*str = '\0';
 			break;
 
 		case USE_POSTGRES_DATES:
 		default:
 			/* traditional date-only style for Postgres */
 			if (DateOrder == DATEORDER_DMY)
-				sprintf(str, "%02d-%02d", tm->tm_mday, tm->tm_mon);
-			else
-				sprintf(str, "%02d-%02d", tm->tm_mon, tm->tm_mday);
-			if (tm->tm_year > 0)
-				sprintf(str + 5, "-%04d", tm->tm_year);
+			{
+				str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+				*str++ = '-';
+				str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
+			}
 			else
-				sprintf(str + 5, "-%04d %s", -(tm->tm_year - 1), "BC");
+			{
+				str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
+				*str++ = '-';
+				str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+			}
+			*str++ = '-';
+			str = pg_ltostr_zeropad(str,
+				(tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
+
+			if (tm->tm_year <= 0)
+			{
+				memcpy(str, " BC", 3); /* Don't copy NUL */
+				str += 3;
+			}
+			*str = '\0';
 			break;
 	}
 }
@@ -3927,13 +4047,15 @@ EncodeDateOnly(struct pg_tm * tm, int style, char *str)
 void
 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 += strlen(str);
-
-	AppendSeconds(str, tm->tm_sec, fsec, MAX_TIME_PRECISION, true);
+	str = pg_ltostr_zeropad(str, tm->tm_hour, 2);
+	*str++ = ':';
+	str = pg_ltostr_zeropad(str, tm->tm_min, 2);
+	*str++ = ':';
+	str = AppendSeconds(str, tm->tm_sec, fsec, MAX_TIME_PRECISION, true);
 
 	if (print_tz)
-		EncodeTimezone(str, tz, style);
+		str = EncodeTimezone(str, tz, style);
+	*str = '\0';
 }
 
 
@@ -3971,38 +4093,56 @@ EncodeDateTime(struct pg_tm * tm, fsec_t fsec, bool print_tz, int tz, const char
 		case USE_ISO_DATES:
 		case USE_XSD_DATES:
 			/* Compatible with ISO-8601 date formats */
-
-			if (style == USE_ISO_DATES)
-				sprintf(str, "%04d-%02d-%02d %02d:%02d:",
-						(tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1),
-						tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min);
-			else
-				sprintf(str, "%04d-%02d-%02dT%02d:%02d:",
-						(tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1),
-						tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min);
-
-			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_mon, 2);
+			*str++ = '-';
+			str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+			*str++ = (style == USE_ISO_DATES) ? ' ' : 'T';
+			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)
-				EncodeTimezone(str, tz, style);
+				str = EncodeTimezone(str, tz, style);
 
 			if (tm->tm_year <= 0)
-				sprintf(str + strlen(str), " BC");
+			{
+				memcpy(str, " BC", 3); /* Don't copy NUL */
+				str += 3;
+			}
+			*str = '\0';
 			break;
 
 		case USE_SQL_DATES:
 			/* Compatible with Oracle/Ingres date formats */
 
 			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
-				sprintf(str, "%02d/%02d", tm->tm_mon, tm->tm_mday);
-
-			sprintf(str + 5, "/%04d %02d:%02d:",
-					(tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1),
-					tm->tm_hour, tm->tm_min);
+			{
+				str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
+				*str++ = '/';
+				str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+			}
 
-			AppendTimestampSeconds(str + strlen(str), tm, fsec);
+			*str++ = '/';
+			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
@@ -4013,36 +4153,55 @@ EncodeDateTime(struct pg_tm * tm, fsec_t fsec, bool print_tz, int tz, const char
 			if (print_tz)
 			{
 				if (tzn)
-					sprintf(str + strlen(str), " %.*s", MAXTZLEN, tzn);
+				{
+					sprintf(str, " %.*s", MAXTZLEN, tzn);
+					str += strlen(str);
+				}
 				else
-					EncodeTimezone(str, tz, style);
+					str = EncodeTimezone(str, tz, style);
 			}
 
 			if (tm->tm_year <= 0)
-				sprintf(str + strlen(str), " BC");
+			{
+				memcpy(str, " BC", 3); /* Don't copy NUL */
+				str += 3;
+			}
+			*str = '\0';
 			break;
 
 		case USE_GERMAN_DATES:
 			/* German variant on European style */
 
-			sprintf(str, "%02d.%02d", tm->tm_mday, tm->tm_mon);
-
-			sprintf(str + 5, ".%04d %02d:%02d:",
-					(tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1),
-					tm->tm_hour, tm->tm_min);
-
-			AppendTimestampSeconds(str + strlen(str), tm, fsec);
+			str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+			*str++ = '.';
+			str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
+			*str++ = '.';
+			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);
 
 			if (print_tz)
 			{
 				if (tzn)
-					sprintf(str + strlen(str), " %.*s", MAXTZLEN, tzn);
+				{
+					sprintf(str, " %.*s", MAXTZLEN, tzn);
+					str += strlen(str);
+				}
 				else
-					EncodeTimezone(str, tz, style);
+					str = EncodeTimezone(str, tz, style);
 			}
 
 			if (tm->tm_year <= 0)
-				sprintf(str + strlen(str), " BC");
+			{
+				memcpy(str, " BC", 3); /* Don't copy NUL */
+				str += 3;
+			}
+			*str = '\0';
 			break;
 
 		case USE_POSTGRES_DATES:
@@ -4053,24 +4212,33 @@ EncodeDateTime(struct pg_tm * tm, fsec_t fsec, bool print_tz, int tz, const char
 			tm->tm_wday = j2day(day);
 
 			memcpy(str, days[tm->tm_wday], 3);
-			strcpy(str + 3, " ");
+			str += 3;
+			*str++ = ' ';
 
 			if (DateOrder == DATEORDER_DMY)
-				sprintf(str + 4, "%02d %3s", tm->tm_mday, months[tm->tm_mon - 1]);
+				sprintf(str, "%02d %3s", tm->tm_mday, months[tm->tm_mon - 1]);
 			else
-				sprintf(str + 4, "%3s %02d", months[tm->tm_mon - 1], tm->tm_mday);
+				sprintf(str, "%3s %02d", months[tm->tm_mon - 1], tm->tm_mday);
+			str += 6;
 
-			sprintf(str + 10, " %02d:%02d:", tm->tm_hour, tm->tm_min);
+			*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);
 
-			AppendTimestampSeconds(str + strlen(str), tm, fsec);
-
-			sprintf(str + strlen(str), " %04d",
-					(tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1));
+			*str++ = ' ';
+			str = pg_ltostr_zeropad(str,
+				(tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
 
 			if (print_tz)
 			{
 				if (tzn)
-					sprintf(str + strlen(str), " %.*s", MAXTZLEN, tzn);
+				{
+					sprintf(str, " %.*s", MAXTZLEN, tzn);
+					str += strlen(str);
+				}
 				else
 				{
 					/*
@@ -4079,13 +4247,17 @@ EncodeDateTime(struct pg_tm * tm, fsec_t fsec, bool print_tz, int tz, const char
 					 * avoid formatting something which would be rejected by
 					 * the date/time parser later. - thomas 2001-10-19
 					 */
-					sprintf(str + strlen(str), " ");
-					EncodeTimezone(str, tz, style);
+					*str++ = ' ';
+					str = EncodeTimezone(str, tz, style);
 				}
 			}
 
 			if (tm->tm_year <= 0)
-				sprintf(str + strlen(str), " BC");
+			{
+				memcpy(str, " BC", 3); /* Don't copy NUL */
+				str += 3;
+			}
+			*str = '\0';
 			break;
 	}
 }
@@ -4242,7 +4414,8 @@ EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str)
 							day_sign, abs(mday),
 							sec_sign, abs(hour), abs(min));
 					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)
 				{
@@ -4252,13 +4425,15 @@ EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str)
 				{
 					sprintf(cp, "%d %d:%02d:", mday, hour, min);
 					cp += strlen(cp);
-					AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+					cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+					*cp = '\0';
 				}
 				else
 				{
 					sprintf(cp, "%d:%02d:", hour, min);
 					cp += strlen(cp);
-					AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+					cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+					*cp = '\0';
 				}
 			}
 			break;
@@ -4284,8 +4459,7 @@ EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str)
 			{
 				if (sec < 0 || fsec < 0)
 					*cp++ = '-';
-				AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
-				cp += strlen(cp);
+				cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
 				*cp++ = 'S';
 				*cp++ = '\0';
 			}
@@ -4311,7 +4485,8 @@ EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str)
 						(minus ? "-" : (is_before ? "+" : "")),
 						abs(hour), abs(min));
 				cp += strlen(cp);
-				AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+				cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+				*cp = '\0';
 			}
 			break;
 
@@ -4337,8 +4512,7 @@ EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str)
 				}
 				else if (is_before)
 					*cp++ = '-';
-				AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
-				cp += strlen(cp);
+				cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
 				sprintf(cp, " sec%s",
 						(abs(sec) != 1 || fsec != 0) ? "s" : "");
 				is_zero = FALSE;
diff --git a/src/backend/utils/adt/numutils.c b/src/backend/utils/adt/numutils.c
index 1dadbd5..6b64a85 100644
--- a/src/backend/utils/adt/numutils.c
+++ b/src/backend/utils/adt/numutils.c
@@ -227,3 +227,165 @@ pg_lltoa(int64 value, char *a)
 		*a-- = swap;
 	}
 }
+
+
+/*
+ * pg_ltostr_zeropad
+ *		Converts 'value' into a decimal string representation of the number.
+ *		'padding' specifies the minimum width of the number. Any extra space
+ *		is filled up by prefixing the number with zeros. The return value is a
+ *		pointer to, the would be end of string. Note that the NUL termination
+ *		char is not written.
+ *
+ * The intended use pattern for this function is to build strings which contain
+ * multiple individual numbers, such as:
+ *
+ *	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: Callers should ensure that 'padding' is above zero.
+ * Note: This function is optimized for the case where the number is not too
+ *		 big to fit inside of the specified padding.
+ * Note: Caller must ensure that 'str' points to enough memory to hold the
+		 result (at least 12 bytes, counting a leading sign and trailing NUL,
+		 or padding + 1 bytes, whichever is larger).
+ */
+char *
+pg_ltostr_zeropad(char *str, int32 value, int32 padding)
+{
+	char	   *start = str;
+	char	   *end = &str[padding];
+	int32		num = value;
+
+	Assert(padding > 0);
+
+	/*
+	 * Handle negative numbers in a special way. We can't just append a '-'
+	 * prefix and reverse the sign as on two's complement machines negative
+	 * numbers can be 1 further from 0 than positive numbers, we do it this way
+	 * so we properly handle the smallest possible value.
+	 */
+	if (num < 0)
+	{
+		*start++ = '-';
+		padding--;
+
+		/*
+		 * Build the number starting at the end. Here remainder will be a
+		 * negative number, we must reverse this sign on this before adding
+		 * '0' in order to get the correct ASCII digit
+		 */
+		while (padding--)
+		{
+			int32		remainder;
+			int32		oldval = num;
+
+			num /= 10;
+			remainder = oldval - num * 10;
+			start[padding] = '0' + -remainder;
+		}
+	}
+	else
+	{
+		/* build the number starting at the end */
+		while (padding--)
+		{
+			int32		remainder;
+			int32		oldval = num;
+
+			num /= 10;
+			remainder = oldval - num * 10;
+			start[padding] = '0' + remainder;
+		}
+	}
+
+	/*
+	 * If padding was not high enough to fit this number then num won't have
+	 * been divided down to zero. We'd better have another go, this time we
+	 * know there won't be any zero padding required so we can just enlist the
+	 * help of pg_ltostr()
+	 */
+	if (num != 0)
+		return pg_ltostr(str, value);
+
+	return end; /* Not NUL terminated */
+}
+
+/*
+ * pg_ltostr
+ *		Converts 'value' into a decimal string representation of the number.
+ *
+ * Caller must ensure that 'str' points to enough memory to hold the result
+ * (at least 12 bytes, counting a leading sign and trailing NUL). The return
+ * value is a pointer to, the would be end of string. The NUL termination char
+ * is NOT written.
+ *
+ * The intended use pattern for this function is to build strings which contain
+ * multiple individual numbers, such as:
+ *
+ *	str = pg_ltostr(str, a);
+ *	*str++ = ' ';
+ *	str = pg_ltostr(str, b);
+ *	*str = '\0';
+ */
+char *
+pg_ltostr(char *str, int32 value)
+{
+	char *start;
+	char *end;
+
+	/*
+	 * Handle negative numbers in a special way. We can't just append a '-'
+	 * prefix and reverse the sign as on two's complement machines negative
+	 * numbers can be 1 further from 0 than positive numbers, we do it this way
+	 * so we properly handle the smallest possible value.
+	 */
+	if (value < 0)
+	{
+		*str++ = '-';
+
+		/* mark the position we must reverse the string from. */
+		start = str;
+
+		/* Compute the result string backwards. */
+		do
+		{
+			int32		remainder;
+			int32		oldval = value;
+
+			value /= 10;
+			remainder = oldval - value * 10;
+			*str++ = '0' + -remainder;
+		} while (value != 0);
+	}
+	else
+	{
+		/* mark the position we must reverse the string from. */
+		start = str;
+		do
+		{
+			int32		remainder;
+			int32		oldval = value;
+
+			value /= 10;
+			remainder = oldval - value * 10;
+			*str++ = '0' + remainder;
+		} while (value != 0);
+	}
+
+	/* Remember the end of string and back up 'str' to the last character. */
+	end = str--;
+
+	/* Reverse string. */
+	while (start < str)
+	{
+		char		swap = *start;
+		*start++ = *str;
+		*str-- = swap;
+	}
+	return end;
+}
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index fc1679e..0f284ae 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -289,6 +289,8 @@ extern int32 pg_atoi(const char *s, int size, int c);
 extern void pg_itoa(int16 i, char *a);
 extern void pg_ltoa(int32 l, char *a);
 extern void pg_lltoa(int64 ll, char *a);
+extern char *pg_ltostr_zeropad(char *str, int32 value, int32 padding);
+extern char *pg_ltostr(char *str, int32 value);
 
 /*
  *		Per-opclass comparison functions for new btrees.  These are
