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);
/* TrimTrailingZeros()
* ... 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,
* but conversations on the lists suggest this isn't desired
* since showing '0.10' is misleading with values of precision(1).
*/
-static void
+#ifndef HAVE_INT64_TIMESTAMP
+static char *
TrimTrailingZeros(char *str)
{
int len = strlen(str);
while (len > 1 && *(str + len - 1) == '0' && *(str + len - 2) != '.')
- {
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.
+ *
+ * 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.
*/
-static void
+static char *
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 (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.
+ *
+ * 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)
{
/*
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);
}
/*
}
/* 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)
{
int hour,
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()
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);
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);
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);
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);
break;
}
+
+ if (tm->tm_year <= 0)
+ {
+ memcpy(str, " BC", 3); /* Don't copy NUL */
+ str += 3;
+ }
+ *str = '\0';
}
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';
}
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);
-
- if (tm->tm_year <= 0)
- sprintf(str + strlen(str), " BC");
+ str = EncodeTimezone(str, tz, style);
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);
-
- AppendTimestampSeconds(str + strlen(str), tm, fsec);
+ {
+ 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);
+ *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
* 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 (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");
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");
break;
case USE_POSTGRES_DATES:
default:
/* Backward-compatible with traditional Postgres abstime dates */
-
day = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday);
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]);
+ {
+ str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+ *str++ = ' ';
+ memcpy(str, months[tm->tm_mon - 1], 3);
+ str += 3;
+ }
else
- sprintf(str + 4, "%3s %02d", months[tm->tm_mon - 1], tm->tm_mday);
-
- sprintf(str + 10, " %02d:%02d:", tm->tm_hour, tm->tm_min);
-
- AppendTimestampSeconds(str + strlen(str), tm, fsec);
-
- sprintf(str + strlen(str), " %04d",
- (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1));
+ {
+ memcpy(str, months[tm->tm_mon - 1], 3);
+ str += 3;
+ *str++ = ' ';
+ str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+ }
+ *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);
+ *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
{
/*
* 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");
break;
}
+
+ if (tm->tm_year <= 0)
+ {
+ memcpy(str, " BC", 3); /* Don't copy NUL */
+ str += 3;
+ }
+ *str = '\0';
}
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)
{
{
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;
{
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';
}
(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;
}
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;