mktime: check signed shifts on long_int and time_t, too
[glibc.git] / stdlib / strfmon_l.c
blobd3e9d710322b1a58b89caccc19b9bc2514b0eaef
1 /* Formatting a monetary value according to the given locale.
2 Copyright (C) 1996,1997,2002,2004,2006,2009,2010 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
4 Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996.
6 The GNU C Library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either
9 version 2.1 of the License, or (at your option) any later version.
11 The GNU C Library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with the GNU C Library; if not, see
18 <http://www.gnu.org/licenses/>. */
20 #include <ctype.h>
21 #include <errno.h>
22 #include <langinfo.h>
23 #include <locale.h>
24 #include <monetary.h>
25 #include "../libio/libioP.h"
26 #include "../libio/strfile.h"
27 #include <printf.h>
28 #include <stdarg.h>
29 #include <stdio.h>
30 #include <string.h>
31 #include "../locale/localeinfo.h"
34 #define out_char(Ch) \
35 do { \
36 if (dest >= s + maxsize - 1) \
37 { \
38 __set_errno (E2BIG); \
39 va_end (ap); \
40 return -1; \
41 } \
42 *dest++ = (Ch); \
43 } while (0)
45 #define out_string(String) \
46 do { \
47 const char *_s = (String); \
48 while (*_s) \
49 out_char (*_s++); \
50 } while (0)
52 #define out_nstring(String, N) \
53 do { \
54 int _n = (N); \
55 const char *_s = (String); \
56 while (_n-- > 0) \
57 out_char (*_s++); \
58 } while (0)
60 #define to_digit(Ch) ((Ch) - '0')
63 /* We use this code also for the extended locale handling where the
64 function gets as an additional argument the locale which has to be
65 used. To access the values we have to redefine the _NL_CURRENT
66 macro. */
67 #undef _NL_CURRENT
68 #define _NL_CURRENT(category, item) \
69 (current->values[_NL_ITEM_INDEX (item)].string)
71 extern int __printf_fp (FILE *, const struct printf_info *,
72 const void *const *);
73 libc_hidden_proto (__printf_fp)
74 /* This function determines the number of digit groups in the output.
75 The definition is in printf_fp.c. */
76 extern unsigned int __guess_grouping (unsigned int intdig_max,
77 const char *grouping, wchar_t sepchar);
80 /* We have to overcome some problems with this implementation. On the
81 one hand the strfmon() function is specified in XPG4 and of course
82 it has to follow this. But on the other hand POSIX.2 specifies
83 some information in the LC_MONETARY category which should be used,
84 too. Some of the information contradicts the information which can
85 be specified in format string. */
86 ssize_t
87 __vstrfmon_l (char *s, size_t maxsize, __locale_t loc, const char *format,
88 va_list ap)
90 struct __locale_data *current = loc->__locales[LC_MONETARY];
91 _IO_strfile f;
92 struct printf_info info;
93 char *dest; /* Pointer so copy the output. */
94 const char *fmt; /* Pointer that walks through format. */
96 dest = s;
97 fmt = format;
99 /* Loop through the format-string. */
100 while (*fmt != '\0')
102 /* The floating-point value to output. */
103 union
105 double dbl;
106 __long_double_t ldbl;
108 fpnum;
109 int int_format;
110 int print_curr_symbol;
111 int left_prec;
112 int left_pad;
113 int right_prec;
114 int group;
115 char pad;
116 int is_long_double;
117 int p_sign_posn;
118 int n_sign_posn;
119 int sign_posn;
120 int other_sign_posn;
121 int left;
122 int is_negative;
123 int sep_by_space;
124 int other_sep_by_space;
125 int cs_precedes;
126 int other_cs_precedes;
127 const char *sign_string;
128 const char *other_sign_string;
129 int done;
130 const char *currency_symbol;
131 size_t currency_symbol_len;
132 long int width;
133 char *startp;
134 const void *ptr;
135 char space_char;
137 /* Process all character which do not introduce a format
138 specification. */
139 if (*fmt != '%')
141 out_char (*fmt++);
142 continue;
145 /* "%%" means a single '%' character. */
146 if (fmt[1] == '%')
148 out_char (*++fmt);
149 ++fmt;
150 continue;
153 /* Defaults for formatting. */
154 int_format = 0; /* Use international curr. symbol */
155 print_curr_symbol = 1; /* Print the currency symbol. */
156 left_prec = -1; /* No left precision specified. */
157 right_prec = -1; /* No right precision specified. */
158 group = 1; /* Print digits grouped. */
159 pad = ' '; /* Fill character is <SP>. */
160 is_long_double = 0; /* Double argument by default. */
161 p_sign_posn = -1; /* This indicates whether the */
162 n_sign_posn = -1; /* '(' flag is given. */
163 width = -1; /* No width specified so far. */
164 left = 0; /* Right justified by default. */
166 /* Parse group characters. */
167 while (1)
169 switch (*++fmt)
171 case '=': /* Set fill character. */
172 pad = *++fmt;
173 if (pad == '\0')
175 /* Premature EOS. */
176 __set_errno (EINVAL);
177 return -1;
179 continue;
180 case '^': /* Don't group digits. */
181 group = 0;
182 continue;
183 case '+': /* Use +/- for sign of number. */
184 if (n_sign_posn != -1)
186 __set_errno (EINVAL);
187 return -1;
189 p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
190 n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
191 continue;
192 case '(': /* Use ( ) for negative sign. */
193 if (n_sign_posn != -1)
195 __set_errno (EINVAL);
196 return -1;
198 p_sign_posn = 0;
199 n_sign_posn = 0;
200 continue;
201 case '!': /* Don't print the currency symbol. */
202 print_curr_symbol = 0;
203 continue;
204 case '-': /* Print left justified. */
205 left = 1;
206 continue;
207 default:
208 /* Will stop the loop. */;
210 break;
213 if (isdigit (*fmt))
215 /* Parse field width. */
216 width = to_digit (*fmt);
218 while (isdigit (*++fmt))
220 int val = to_digit (*fmt);
222 if (width > LONG_MAX / 10
223 || (width == LONG_MAX && val > LONG_MAX % 10))
225 __set_errno (E2BIG);
226 return -1;
229 width = width * 10 + val;
232 /* If we don't have enough room for the demanded width we
233 can stop now and return an error. */
234 if (width >= maxsize - (dest - s))
236 __set_errno (E2BIG);
237 return -1;
241 /* Recognize left precision. */
242 if (*fmt == '#')
244 if (!isdigit (*++fmt))
246 __set_errno (EINVAL);
247 return -1;
249 left_prec = to_digit (*fmt);
251 while (isdigit (*++fmt))
253 left_prec *= 10;
254 left_prec += to_digit (*fmt);
258 /* Recognize right precision. */
259 if (*fmt == '.')
261 if (!isdigit (*++fmt))
263 __set_errno (EINVAL);
264 return -1;
266 right_prec = to_digit (*fmt);
268 while (isdigit (*++fmt))
270 right_prec *= 10;
271 right_prec += to_digit (*fmt);
275 /* Handle modifier. This is an extension. */
276 if (*fmt == 'L')
278 ++fmt;
279 if (!__ldbl_is_dbl)
280 is_long_double = 1;
283 /* Handle format specifier. */
284 char int_symbol[4];
285 switch (*fmt++)
287 case 'i': { /* Use international currency symbol. */
288 const char *int_curr_symbol;
290 int_curr_symbol = _NL_CURRENT (LC_MONETARY, INT_CURR_SYMBOL);
291 strncpy(int_symbol, int_curr_symbol, 3);
292 int_symbol[3] = '\0';
294 currency_symbol_len = 3;
295 currency_symbol = &int_symbol[0];
296 space_char = int_curr_symbol[3];
297 int_format = 1;
298 break;
300 case 'n': /* Use national currency symbol. */
301 currency_symbol = _NL_CURRENT (LC_MONETARY, CURRENCY_SYMBOL);
302 currency_symbol_len = strlen (currency_symbol);
303 space_char = ' ';
304 int_format = 0;
305 break;
306 default: /* Any unrecognized format is an error. */
307 __set_errno (EINVAL);
308 return -1;
311 /* If not specified by the format string now find the values for
312 the format specification. */
313 if (p_sign_posn == -1)
314 p_sign_posn = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SIGN_POSN : P_SIGN_POSN);
315 if (n_sign_posn == -1)
316 n_sign_posn = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SIGN_POSN : N_SIGN_POSN);
318 if (right_prec == -1)
320 right_prec = *_NL_CURRENT (LC_MONETARY, int_format ? INT_FRAC_DIGITS : FRAC_DIGITS);
322 if (right_prec == CHAR_MAX)
323 right_prec = 2;
326 /* If we have to print the digits grouped determine how many
327 extra characters this means. */
328 if (group && left_prec != -1)
329 left_prec += __guess_grouping (left_prec,
330 _NL_CURRENT (LC_MONETARY, MON_GROUPING),
331 *_NL_CURRENT (LC_MONETARY,
332 MON_THOUSANDS_SEP));
334 /* Now it's time to get the value. */
335 if (is_long_double == 1)
337 fpnum.ldbl = va_arg (ap, long double);
338 is_negative = fpnum.ldbl < 0;
339 if (is_negative)
340 fpnum.ldbl = -fpnum.ldbl;
342 else
344 fpnum.dbl = va_arg (ap, double);
345 is_negative = fpnum.dbl < 0;
346 if (is_negative)
347 fpnum.dbl = -fpnum.dbl;
350 /* We now know the sign of the value and can determine the format. */
351 if (is_negative)
353 sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
354 /* If the locale does not specify a character for the
355 negative sign we use a '-'. */
356 if (*sign_string == '\0')
357 sign_string = (const char *) "-";
358 cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_CS_PRECEDES : N_CS_PRECEDES);
359 sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SEP_BY_SPACE : N_SEP_BY_SPACE);
360 sign_posn = n_sign_posn;
362 other_sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
363 other_cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_CS_PRECEDES : P_CS_PRECEDES);
364 other_sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SEP_BY_SPACE : P_SEP_BY_SPACE);
365 other_sign_posn = p_sign_posn;
367 else
369 sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
370 cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_CS_PRECEDES : P_CS_PRECEDES);
371 sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SEP_BY_SPACE : P_SEP_BY_SPACE);
372 sign_posn = p_sign_posn;
374 other_sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
375 if (*other_sign_string == '\0')
376 other_sign_string = (const char *) "-";
377 other_cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_CS_PRECEDES : N_CS_PRECEDES);
378 other_sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SEP_BY_SPACE : N_SEP_BY_SPACE);
379 other_sign_posn = n_sign_posn;
382 /* Set default values for unspecified information. */
383 if (cs_precedes != 0)
384 cs_precedes = 1;
385 if (other_cs_precedes != 0)
386 other_cs_precedes = 1;
387 if (sep_by_space == CHAR_MAX)
388 sep_by_space = 0;
389 if (other_sep_by_space == CHAR_MAX)
390 other_sep_by_space = 0;
391 if (sign_posn == CHAR_MAX)
392 sign_posn = 1;
393 if (other_sign_posn == CHAR_MAX)
394 other_sign_posn = 1;
396 /* Check for degenerate cases */
397 if (sep_by_space == 2)
399 if (sign_posn == 0 ||
400 (sign_posn == 1 && !cs_precedes) ||
401 (sign_posn == 2 && cs_precedes))
402 /* sign and symbol are not adjacent, so no separator */
403 sep_by_space = 0;
405 if (other_sep_by_space == 2)
407 if (other_sign_posn == 0 ||
408 (other_sign_posn == 1 && !other_cs_precedes) ||
409 (other_sign_posn == 2 && other_cs_precedes))
410 /* sign and symbol are not adjacent, so no separator */
411 other_sep_by_space = 0;
414 /* Set the left precision and padding needed for alignment */
415 if (left_prec == -1)
417 left_prec = 0;
418 left_pad = 0;
420 else
422 /* Set left_pad to number of spaces needed to align positive
423 and negative formats */
425 int left_bytes = 0;
426 int other_left_bytes = 0;
428 /* Work out number of bytes for currency string and separator
429 preceding the value */
430 if (cs_precedes)
432 left_bytes += currency_symbol_len;
433 if (sep_by_space != 0)
434 ++left_bytes;
437 if (other_cs_precedes)
439 other_left_bytes += currency_symbol_len;
440 if (other_sep_by_space != 0)
441 ++other_left_bytes;
444 /* Work out number of bytes for the sign (or left parenthesis)
445 preceding the value */
446 if (sign_posn == 0 && is_negative)
447 ++left_bytes;
448 else if (sign_posn == 1)
449 left_bytes += strlen (sign_string);
450 else if (cs_precedes && (sign_posn == 3 || sign_posn == 4))
451 left_bytes += strlen (sign_string);
453 if (other_sign_posn == 0 && !is_negative)
454 ++other_left_bytes;
455 else if (other_sign_posn == 1)
456 other_left_bytes += strlen (other_sign_string);
457 else if (other_cs_precedes &&
458 (other_sign_posn == 3 || other_sign_posn == 4))
459 other_left_bytes += strlen (other_sign_string);
461 /* Compare the number of bytes preceding the value for
462 each format, and set the padding accordingly */
463 if (other_left_bytes > left_bytes)
464 left_pad = other_left_bytes - left_bytes;
465 else
466 left_pad = 0;
469 /* Perhaps we'll someday make these things configurable so
470 better start using symbolic names now. */
471 #define left_paren '('
472 #define right_paren ')'
474 startp = dest; /* Remember start so we can compute length. */
476 while (left_pad-- > 0)
477 out_char (' ');
479 if (sign_posn == 0 && is_negative)
480 out_char (left_paren);
482 if (cs_precedes)
484 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 4
485 && sign_posn != 5)
487 out_string (sign_string);
488 if (sep_by_space == 2)
489 out_char (' ');
492 if (print_curr_symbol)
493 out_string (currency_symbol);
495 if (sign_posn == 4)
497 if (print_curr_symbol && sep_by_space == 2)
498 out_char (space_char);
499 out_string (sign_string);
500 if (sep_by_space == 1)
501 /* POSIX.2 and SUS are not clear on this case, but C99
502 says a space follows the adjacent-symbol-and-sign */
503 out_char (' ');
505 else
506 if (print_curr_symbol && sep_by_space == 1)
507 out_char (space_char);
509 else
510 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 3
511 && sign_posn != 4 && sign_posn != 5)
512 out_string (sign_string);
514 /* Print the number. */
515 #ifdef _IO_MTSAFE_IO
516 f._sbf._f._lock = NULL;
517 #endif
518 INTUSE(_IO_init) (&f._sbf._f, 0);
519 _IO_JUMPS (&f._sbf) = &_IO_str_jumps;
520 INTUSE(_IO_str_init_static) (&f, dest,
521 (s + maxsize) - dest, dest);
522 /* We clear the last available byte so we can find out whether
523 the numeric representation is too long. */
524 s[maxsize - 1] = '\0';
526 memset (&info, '\0', sizeof (info));
527 info.prec = right_prec;
528 info.width = left_prec + (right_prec ? (right_prec + 1) : 0);
529 info.spec = 'f';
530 info.is_long_double = is_long_double;
531 info.group = group;
532 info.pad = pad;
533 info.extra = 1; /* This means use values from LC_MONETARY. */
535 ptr = &fpnum;
536 done = __printf_fp (&f._sbf._f, &info, &ptr);
537 if (done < 0)
538 return -1;
540 if (s[maxsize - 1] != '\0')
542 __set_errno (E2BIG);
543 return -1;
546 dest += done;
548 if (!cs_precedes)
550 if (sign_posn == 3)
552 if (sep_by_space == 1)
553 out_char (' ');
554 out_string (sign_string);
557 if (print_curr_symbol)
559 if ((sign_posn == 3 && sep_by_space == 2)
560 || (sign_posn == 4 && sep_by_space == 1)
561 || (sign_posn == 2 && sep_by_space == 1)
562 || (sign_posn == 1 && sep_by_space == 1)
563 || (sign_posn == 0 && sep_by_space == 1))
564 out_char (space_char);
565 out_nstring (currency_symbol, currency_symbol_len);
568 if (sign_posn == 4)
570 if (sep_by_space == 2)
571 out_char (' ');
572 out_string (sign_string);
576 if (sign_posn == 2)
578 if (sep_by_space == 2)
579 out_char (' ');
580 out_string (sign_string);
583 if (sign_posn == 0 && is_negative)
584 out_char (right_paren);
586 /* Now test whether the output width is filled. */
587 if (dest - startp < width)
589 if (left)
590 /* We simply have to fill using spaces. */
592 out_char (' ');
593 while (dest - startp < width);
594 else
596 long int dist = width - (dest - startp);
597 for (char *cp = dest - 1; cp >= startp; --cp)
598 cp[dist] = cp[0];
600 dest += dist;
603 startp[--dist] = ' ';
604 while (dist > 0);
609 /* Terminate the string. */
610 *dest = '\0';
612 return dest - s;
615 ssize_t
616 ___strfmon_l (char *s, size_t maxsize, __locale_t loc, const char *format, ...)
618 va_list ap;
620 va_start (ap, format);
622 ssize_t res = __vstrfmon_l (s, maxsize, loc, format, ap);
624 va_end (ap);
626 return res;
628 ldbl_strong_alias (___strfmon_l, __strfmon_l)
629 ldbl_weak_alias (___strfmon_l, strfmon_l)