Commit 6743a878 authored by Tom Lane's avatar Tom Lane

Support more locale-specific formatting options in cash_out().

The POSIX spec defines locale fields for controlling the ordering of the
value, sign, and currency symbol in monetary output, but cash_out only
supported a small subset of these options.  Fully implement p/n_sign_posn,
p/n_cs_precedes, and p/n_sep_by_space per spec.  Fix up cash_in so that
it will accept all these format variants.

Also, make sure that thousands_sep is only inserted to the left of the
decimal point, as required by spec.

Per bug #6144 from Eduard Kracmar and discussion of bug #6277.  This patch
includes some ideas from Alexander Lakhin's proposed patch, though it is
very different in detail.
parent eb5834d5
...@@ -150,6 +150,8 @@ cash_in(PG_FUNCTION_ARGS) ...@@ -150,6 +150,8 @@ cash_in(PG_FUNCTION_ARGS)
s++; s++;
if (strncmp(s, csymbol, strlen(csymbol)) == 0) if (strncmp(s, csymbol, strlen(csymbol)) == 0)
s += strlen(csymbol); s += strlen(csymbol);
while (isspace((unsigned char) *s))
s++;
#ifdef CASHDEBUG #ifdef CASHDEBUG
printf("cashin- string is '%s'\n", s); printf("cashin- string is '%s'\n", s);
...@@ -180,6 +182,8 @@ cash_in(PG_FUNCTION_ARGS) ...@@ -180,6 +182,8 @@ cash_in(PG_FUNCTION_ARGS)
s++; s++;
if (strncmp(s, csymbol, strlen(csymbol)) == 0) if (strncmp(s, csymbol, strlen(csymbol)) == 0)
s += strlen(csymbol); s += strlen(csymbol);
while (isspace((unsigned char) *s))
s++;
#ifdef CASHDEBUG #ifdef CASHDEBUG
printf("cashin- string is '%s'\n", s); printf("cashin- string is '%s'\n", s);
...@@ -218,10 +222,11 @@ cash_in(PG_FUNCTION_ARGS) ...@@ -218,10 +222,11 @@ cash_in(PG_FUNCTION_ARGS)
/* /*
* should only be trailing digits followed by whitespace, right paren, * should only be trailing digits followed by whitespace, right paren,
* or possibly a trailing minus sign * trailing sign, and/or trailing currency symbol
*/ */
while (isdigit((unsigned char) *s)) while (isdigit((unsigned char) *s))
s++; s++;
while (*s) while (*s)
{ {
if (isspace((unsigned char) *s) || *s == ')') if (isspace((unsigned char) *s) || *s == ')')
...@@ -231,6 +236,10 @@ cash_in(PG_FUNCTION_ARGS) ...@@ -231,6 +236,10 @@ cash_in(PG_FUNCTION_ARGS)
sgn = -1; sgn = -1;
s += strlen(nsymbol); s += strlen(nsymbol);
} }
else if (strncmp(s, psymbol, strlen(psymbol)) == 0)
s += strlen(psymbol);
else if (strncmp(s, csymbol, strlen(csymbol)) == 0)
s += strlen(csymbol);
else else
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
...@@ -259,15 +268,16 @@ cash_out(PG_FUNCTION_ARGS) ...@@ -259,15 +268,16 @@ cash_out(PG_FUNCTION_ARGS)
char *result; char *result;
char buf[128]; char buf[128];
char *bufptr; char *bufptr;
bool minus = false;
int digit_pos; int digit_pos;
int points, int points,
mon_group; mon_group;
char dsymbol; char dsymbol;
const char *ssymbol, const char *ssymbol,
*csymbol, *csymbol,
*nsymbol; *signsymbol;
char convention; char sign_posn,
cs_precedes,
sep_by_space;
struct lconv *lconvert = PGLC_localeconv(); struct lconv *lconvert = PGLC_localeconv();
/* see comments about frac_digits in cash_in() */ /* see comments about frac_digits in cash_in() */
...@@ -283,8 +293,6 @@ cash_out(PG_FUNCTION_ARGS) ...@@ -283,8 +293,6 @@ cash_out(PG_FUNCTION_ARGS)
if (mon_group <= 0 || mon_group > 6) if (mon_group <= 0 || mon_group > 6)
mon_group = 3; mon_group = 3;
convention = lconvert->n_sign_posn;
/* we restrict dsymbol to be a single byte, but not the other symbols */ /* we restrict dsymbol to be a single byte, but not the other symbols */
if (*lconvert->mon_decimal_point != '\0' && if (*lconvert->mon_decimal_point != '\0' &&
lconvert->mon_decimal_point[1] == '\0') lconvert->mon_decimal_point[1] == '\0')
...@@ -296,16 +304,26 @@ cash_out(PG_FUNCTION_ARGS) ...@@ -296,16 +304,26 @@ cash_out(PG_FUNCTION_ARGS)
else /* ssymbol should not equal dsymbol */ else /* ssymbol should not equal dsymbol */
ssymbol = (dsymbol != ',') ? "," : "."; ssymbol = (dsymbol != ',') ? "," : ".";
csymbol = (*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$"; csymbol = (*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$";
nsymbol = (*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-";
/* we work with positive amounts and add the minus sign at the end */
if (value < 0) if (value < 0)
{ {
minus = true; /* make the amount positive for digit-reconstruction loop */
value = -value; value = -value;
/* set up formatting data */
signsymbol = (*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-";
sign_posn = lconvert->n_sign_posn;
cs_precedes = lconvert->n_cs_precedes;
sep_by_space = lconvert->n_sep_by_space;
}
else
{
signsymbol = lconvert->positive_sign;
sign_posn = lconvert->p_sign_posn;
cs_precedes = lconvert->p_cs_precedes;
sep_by_space = lconvert->p_sep_by_space;
} }
/* we build the result string right-to-left in buf[] */ /* we build the digits+decimal-point+sep string right-to-left in buf[] */
bufptr = buf + sizeof(buf) - 1; bufptr = buf + sizeof(buf) - 1;
*bufptr = '\0'; *bufptr = '\0';
...@@ -320,12 +338,12 @@ cash_out(PG_FUNCTION_ARGS) ...@@ -320,12 +338,12 @@ cash_out(PG_FUNCTION_ARGS)
{ {
if (points && digit_pos == 0) if (points && digit_pos == 0)
{ {
/* insert decimal point */ /* insert decimal point, but not if value cannot be fractional */
*(--bufptr) = dsymbol; *(--bufptr) = dsymbol;
} }
else if (digit_pos < points && (digit_pos % mon_group) == 0) else if (digit_pos < 0 && (digit_pos % mon_group) == 0)
{ {
/* insert thousands sep */ /* insert thousands sep, but only to left of radix point */
bufptr -= strlen(ssymbol); bufptr -= strlen(ssymbol);
memcpy(bufptr, ssymbol, strlen(ssymbol)); memcpy(bufptr, ssymbol, strlen(ssymbol));
} }
...@@ -335,27 +353,111 @@ cash_out(PG_FUNCTION_ARGS) ...@@ -335,27 +353,111 @@ cash_out(PG_FUNCTION_ARGS)
digit_pos--; digit_pos--;
} while (value || digit_pos >= 0); } while (value || digit_pos >= 0);
/* prepend csymbol */ /*----------
bufptr -= strlen(csymbol); * Now, attach currency symbol and sign symbol in the correct order.
memcpy(bufptr, csymbol, strlen(csymbol)); *
* The POSIX spec defines these values controlling this code:
*
* p/n_sign_posn:
* 0 Parentheses enclose the quantity and the currency_symbol.
* 1 The sign string precedes the quantity and the currency_symbol.
* 2 The sign string succeeds the quantity and the currency_symbol.
* 3 The sign string precedes the currency_symbol.
* 4 The sign string succeeds the currency_symbol.
*
* p/n_cs_precedes: 0 means currency symbol after value, else before it.
*
* p/n_sep_by_space:
* 0 No <space> separates the currency symbol and value.
* 1 If the currency symbol and sign string are adjacent, a <space>
* separates them from the value; otherwise, a <space> separates
* the currency symbol from the value.
* 2 If the currency symbol and sign string are adjacent, a <space>
* separates them; otherwise, a <space> separates the sign string
* from the value.
*----------
*/
result = palloc(strlen(bufptr) + strlen(csymbol) + strlen(signsymbol) + 4);
/* see if we need to signify negative amount */ switch (sign_posn)
if (minus)
{ {
result = palloc(strlen(bufptr) + strlen(nsymbol) + 3); case 0:
if (cs_precedes)
/* Position code of 0 means use parens */ sprintf(result, "(%s%s%s)",
if (convention == 0) csymbol,
sprintf(result, "(%s)", bufptr); (sep_by_space == 1) ? " " : "",
else if (convention == 2) bufptr);
sprintf(result, "%s%s", bufptr, nsymbol);
else else
sprintf(result, "%s%s", nsymbol, bufptr); sprintf(result, "(%s%s%s)",
} bufptr,
(sep_by_space == 1) ? " " : "",
csymbol);
break;
case 1:
default:
if (cs_precedes)
sprintf(result, "%s%s%s%s%s",
signsymbol,
(sep_by_space == 2) ? " " : "",
csymbol,
(sep_by_space == 1) ? " " : "",
bufptr);
else else
{ sprintf(result, "%s%s%s%s%s",
/* just emit what we have */ signsymbol,
result = pstrdup(bufptr); (sep_by_space == 2) ? " " : "",
bufptr,
(sep_by_space == 1) ? " " : "",
csymbol);
break;
case 2:
if (cs_precedes)
sprintf(result, "%s%s%s%s%s",
csymbol,
(sep_by_space == 1) ? " " : "",
bufptr,
(sep_by_space == 2) ? " " : "",
signsymbol);
else
sprintf(result, "%s%s%s%s%s",
bufptr,
(sep_by_space == 1) ? " " : "",
csymbol,
(sep_by_space == 2) ? " " : "",
signsymbol);
break;
case 3:
if (cs_precedes)
sprintf(result, "%s%s%s%s%s",
signsymbol,
(sep_by_space == 2) ? " " : "",
csymbol,
(sep_by_space == 1) ? " " : "",
bufptr);
else
sprintf(result, "%s%s%s%s%s",
bufptr,
(sep_by_space == 1) ? " " : "",
signsymbol,
(sep_by_space == 2) ? " " : "",
csymbol);
break;
case 4:
if (cs_precedes)
sprintf(result, "%s%s%s%s%s",
csymbol,
(sep_by_space == 2) ? " " : "",
signsymbol,
(sep_by_space == 1) ? " " : "",
bufptr);
else
sprintf(result, "%s%s%s%s%s",
bufptr,
(sep_by_space == 1) ? " " : "",
csymbol,
(sep_by_space == 2) ? " " : "",
signsymbol);
break;
} }
PG_RETURN_CSTRING(result); PG_RETURN_CSTRING(result);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment