Skip to content

Commit 6743a87

Browse files
committed
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.
1 parent eb5834d commit 6743a87

File tree

1 file changed

+134
-32
lines changed

1 file changed

+134
-32
lines changed

src/backend/utils/adt/cash.c

Lines changed: 134 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ cash_in(PG_FUNCTION_ARGS)
150150
s++;
151151
if (strncmp(s, csymbol, strlen(csymbol)) == 0)
152152
s += strlen(csymbol);
153+
while (isspace((unsigned char) *s))
154+
s++;
153155

154156
#ifdef CASHDEBUG
155157
printf("cashin- string is '%s'\n", s);
@@ -180,6 +182,8 @@ cash_in(PG_FUNCTION_ARGS)
180182
s++;
181183
if (strncmp(s, csymbol, strlen(csymbol)) == 0)
182184
s += strlen(csymbol);
185+
while (isspace((unsigned char) *s))
186+
s++;
183187

184188
#ifdef CASHDEBUG
185189
printf("cashin- string is '%s'\n", s);
@@ -218,10 +222,11 @@ cash_in(PG_FUNCTION_ARGS)
218222

219223
/*
220224
* should only be trailing digits followed by whitespace, right paren,
221-
* or possibly a trailing minus sign
225+
* trailing sign, and/or trailing currency symbol
222226
*/
223227
while (isdigit((unsigned char) *s))
224228
s++;
229+
225230
while (*s)
226231
{
227232
if (isspace((unsigned char) *s) || *s == ')')
@@ -231,6 +236,10 @@ cash_in(PG_FUNCTION_ARGS)
231236
sgn = -1;
232237
s += strlen(nsymbol);
233238
}
239+
else if (strncmp(s, psymbol, strlen(psymbol)) == 0)
240+
s += strlen(psymbol);
241+
else if (strncmp(s, csymbol, strlen(csymbol)) == 0)
242+
s += strlen(csymbol);
234243
else
235244
ereport(ERROR,
236245
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
@@ -259,15 +268,16 @@ cash_out(PG_FUNCTION_ARGS)
259268
char *result;
260269
char buf[128];
261270
char *bufptr;
262-
bool minus = false;
263271
int digit_pos;
264272
int points,
265273
mon_group;
266274
char dsymbol;
267275
const char *ssymbol,
268276
*csymbol,
269-
*nsymbol;
270-
char convention;
277+
*signsymbol;
278+
char sign_posn,
279+
cs_precedes,
280+
sep_by_space;
271281
struct lconv *lconvert = PGLC_localeconv();
272282

273283
/* see comments about frac_digits in cash_in() */
@@ -283,8 +293,6 @@ cash_out(PG_FUNCTION_ARGS)
283293
if (mon_group <= 0 || mon_group > 6)
284294
mon_group = 3;
285295

286-
convention = lconvert->n_sign_posn;
287-
288296
/* we restrict dsymbol to be a single byte, but not the other symbols */
289297
if (*lconvert->mon_decimal_point != '\0' &&
290298
lconvert->mon_decimal_point[1] == '\0')
@@ -296,16 +304,26 @@ cash_out(PG_FUNCTION_ARGS)
296304
else /* ssymbol should not equal dsymbol */
297305
ssymbol = (dsymbol != ',') ? "," : ".";
298306
csymbol = (*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$";
299-
nsymbol = (*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-";
300307

301-
/* we work with positive amounts and add the minus sign at the end */
302308
if (value < 0)
303309
{
304-
minus = true;
310+
/* make the amount positive for digit-reconstruction loop */
305311
value = -value;
312+
/* set up formatting data */
313+
signsymbol = (*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-";
314+
sign_posn = lconvert->n_sign_posn;
315+
cs_precedes = lconvert->n_cs_precedes;
316+
sep_by_space = lconvert->n_sep_by_space;
317+
}
318+
else
319+
{
320+
signsymbol = lconvert->positive_sign;
321+
sign_posn = lconvert->p_sign_posn;
322+
cs_precedes = lconvert->p_cs_precedes;
323+
sep_by_space = lconvert->p_sep_by_space;
306324
}
307325

308-
/* we build the result string right-to-left in buf[] */
326+
/* we build the digits+decimal-point+sep string right-to-left in buf[] */
309327
bufptr = buf + sizeof(buf) - 1;
310328
*bufptr = '\0';
311329

@@ -320,12 +338,12 @@ cash_out(PG_FUNCTION_ARGS)
320338
{
321339
if (points && digit_pos == 0)
322340
{
323-
/* insert decimal point */
341+
/* insert decimal point, but not if value cannot be fractional */
324342
*(--bufptr) = dsymbol;
325343
}
326-
else if (digit_pos < points && (digit_pos % mon_group) == 0)
344+
else if (digit_pos < 0 && (digit_pos % mon_group) == 0)
327345
{
328-
/* insert thousands sep */
346+
/* insert thousands sep, but only to left of radix point */
329347
bufptr -= strlen(ssymbol);
330348
memcpy(bufptr, ssymbol, strlen(ssymbol));
331349
}
@@ -335,27 +353,111 @@ cash_out(PG_FUNCTION_ARGS)
335353
digit_pos--;
336354
} while (value || digit_pos >= 0);
337355

338-
/* prepend csymbol */
339-
bufptr -= strlen(csymbol);
340-
memcpy(bufptr, csymbol, strlen(csymbol));
341-
342-
/* see if we need to signify negative amount */
343-
if (minus)
344-
{
345-
result = palloc(strlen(bufptr) + strlen(nsymbol) + 3);
356+
/*----------
357+
* Now, attach currency symbol and sign symbol in the correct order.
358+
*
359+
* The POSIX spec defines these values controlling this code:
360+
*
361+
* p/n_sign_posn:
362+
* 0 Parentheses enclose the quantity and the currency_symbol.
363+
* 1 The sign string precedes the quantity and the currency_symbol.
364+
* 2 The sign string succeeds the quantity and the currency_symbol.
365+
* 3 The sign string precedes the currency_symbol.
366+
* 4 The sign string succeeds the currency_symbol.
367+
*
368+
* p/n_cs_precedes: 0 means currency symbol after value, else before it.
369+
*
370+
* p/n_sep_by_space:
371+
* 0 No <space> separates the currency symbol and value.
372+
* 1 If the currency symbol and sign string are adjacent, a <space>
373+
* separates them from the value; otherwise, a <space> separates
374+
* the currency symbol from the value.
375+
* 2 If the currency symbol and sign string are adjacent, a <space>
376+
* separates them; otherwise, a <space> separates the sign string
377+
* from the value.
378+
*----------
379+
*/
380+
result = palloc(strlen(bufptr) + strlen(csymbol) + strlen(signsymbol) + 4);
346381

347-
/* Position code of 0 means use parens */
348-
if (convention == 0)
349-
sprintf(result, "(%s)", bufptr);
350-
else if (convention == 2)
351-
sprintf(result, "%s%s", bufptr, nsymbol);
352-
else
353-
sprintf(result, "%s%s", nsymbol, bufptr);
354-
}
355-
else
382+
switch (sign_posn)
356383
{
357-
/* just emit what we have */
358-
result = pstrdup(bufptr);
384+
case 0:
385+
if (cs_precedes)
386+
sprintf(result, "(%s%s%s)",
387+
csymbol,
388+
(sep_by_space == 1) ? " " : "",
389+
bufptr);
390+
else
391+
sprintf(result, "(%s%s%s)",
392+
bufptr,
393+
(sep_by_space == 1) ? " " : "",
394+
csymbol);
395+
break;
396+
case 1:
397+
default:
398+
if (cs_precedes)
399+
sprintf(result, "%s%s%s%s%s",
400+
signsymbol,
401+
(sep_by_space == 2) ? " " : "",
402+
csymbol,
403+
(sep_by_space == 1) ? " " : "",
404+
bufptr);
405+
else
406+
sprintf(result, "%s%s%s%s%s",
407+
signsymbol,
408+
(sep_by_space == 2) ? " " : "",
409+
bufptr,
410+
(sep_by_space == 1) ? " " : "",
411+
csymbol);
412+
break;
413+
case 2:
414+
if (cs_precedes)
415+
sprintf(result, "%s%s%s%s%s",
416+
csymbol,
417+
(sep_by_space == 1) ? " " : "",
418+
bufptr,
419+
(sep_by_space == 2) ? " " : "",
420+
signsymbol);
421+
else
422+
sprintf(result, "%s%s%s%s%s",
423+
bufptr,
424+
(sep_by_space == 1) ? " " : "",
425+
csymbol,
426+
(sep_by_space == 2) ? " " : "",
427+
signsymbol);
428+
break;
429+
case 3:
430+
if (cs_precedes)
431+
sprintf(result, "%s%s%s%s%s",
432+
signsymbol,
433+
(sep_by_space == 2) ? " " : "",
434+
csymbol,
435+
(sep_by_space == 1) ? " " : "",
436+
bufptr);
437+
else
438+
sprintf(result, "%s%s%s%s%s",
439+
bufptr,
440+
(sep_by_space == 1) ? " " : "",
441+
signsymbol,
442+
(sep_by_space == 2) ? " " : "",
443+
csymbol);
444+
break;
445+
case 4:
446+
if (cs_precedes)
447+
sprintf(result, "%s%s%s%s%s",
448+
csymbol,
449+
(sep_by_space == 2) ? " " : "",
450+
signsymbol,
451+
(sep_by_space == 1) ? " " : "",
452+
bufptr);
453+
else
454+
sprintf(result, "%s%s%s%s%s",
455+
bufptr,
456+
(sep_by_space == 1) ? " " : "",
457+
csymbol,
458+
(sep_by_space == 2) ? " " : "",
459+
signsymbol);
460+
break;
359461
}
360462

361463
PG_RETURN_CSTRING(result);

0 commit comments

Comments
 (0)