Fix year 2039 bug for localtime with 64-bit time_t (bug 22639).
[glibc.git] / stdlib / strfmon_l.c
blobcd3796ced9805dbe57d26713f16555d5bc77058d
1 /* Formatting a monetary value according to the given locale.
2 Copyright (C) 1996-2018 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)
72 /* We have to overcome some problems with this implementation. On the
73 one hand the strfmon() function is specified in XPG4 and of course
74 it has to follow this. But on the other hand POSIX.2 specifies
75 some information in the LC_MONETARY category which should be used,
76 too. Some of the information contradicts the information which can
77 be specified in format string. */
78 ssize_t
79 __vstrfmon_l (char *s, size_t maxsize, locale_t loc, const char *format,
80 va_list ap)
82 struct __locale_data *current = loc->__locales[LC_MONETARY];
83 _IO_strfile f;
84 struct printf_info info;
85 char *dest; /* Pointer so copy the output. */
86 const char *fmt; /* Pointer that walks through format. */
88 dest = s;
89 fmt = format;
91 /* Loop through the format-string. */
92 while (*fmt != '\0')
94 /* The floating-point value to output. */
95 union
97 double dbl;
98 long double ldbl;
100 fpnum;
101 int int_format;
102 int print_curr_symbol;
103 int left_prec;
104 int left_pad;
105 int right_prec;
106 int group;
107 char pad;
108 int is_long_double;
109 int p_sign_posn;
110 int n_sign_posn;
111 int sign_posn;
112 int other_sign_posn;
113 int left;
114 int is_negative;
115 int sep_by_space;
116 int other_sep_by_space;
117 int cs_precedes;
118 int other_cs_precedes;
119 const char *sign_string;
120 const char *other_sign_string;
121 int done;
122 const char *currency_symbol;
123 size_t currency_symbol_len;
124 long int width;
125 char *startp;
126 const void *ptr;
127 char space_char;
129 /* Process all character which do not introduce a format
130 specification. */
131 if (*fmt != '%')
133 out_char (*fmt++);
134 continue;
137 /* "%%" means a single '%' character. */
138 if (fmt[1] == '%')
140 out_char (*++fmt);
141 ++fmt;
142 continue;
145 /* Defaults for formatting. */
146 int_format = 0; /* Use international curr. symbol */
147 print_curr_symbol = 1; /* Print the currency symbol. */
148 left_prec = -1; /* No left precision specified. */
149 right_prec = -1; /* No right precision specified. */
150 group = 1; /* Print digits grouped. */
151 pad = ' '; /* Fill character is <SP>. */
152 is_long_double = 0; /* Double argument by default. */
153 p_sign_posn = -2; /* This indicates whether the */
154 n_sign_posn = -2; /* '(' flag is given. */
155 width = -1; /* No width specified so far. */
156 left = 0; /* Right justified by default. */
158 /* Parse group characters. */
159 while (1)
161 switch (*++fmt)
163 case '=': /* Set fill character. */
164 pad = *++fmt;
165 if (pad == '\0')
167 /* Premature EOS. */
168 __set_errno (EINVAL);
169 return -1;
171 continue;
172 case '^': /* Don't group digits. */
173 group = 0;
174 continue;
175 case '+': /* Use +/- for sign of number. */
176 if (n_sign_posn != -2)
178 __set_errno (EINVAL);
179 return -1;
181 p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
182 n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
183 continue;
184 case '(': /* Use ( ) for negative sign. */
185 if (n_sign_posn != -2)
187 __set_errno (EINVAL);
188 return -1;
190 p_sign_posn = 0;
191 n_sign_posn = 0;
192 continue;
193 case '!': /* Don't print the currency symbol. */
194 print_curr_symbol = 0;
195 continue;
196 case '-': /* Print left justified. */
197 left = 1;
198 continue;
199 default:
200 /* Will stop the loop. */;
202 break;
205 if (isdigit (*fmt))
207 /* Parse field width. */
208 width = to_digit (*fmt);
210 while (isdigit (*++fmt))
212 int val = to_digit (*fmt);
214 if (width > LONG_MAX / 10
215 || (width == LONG_MAX && val > LONG_MAX % 10))
217 __set_errno (E2BIG);
218 return -1;
221 width = width * 10 + val;
224 /* If we don't have enough room for the demanded width we
225 can stop now and return an error. */
226 if (width >= maxsize - (dest - s))
228 __set_errno (E2BIG);
229 return -1;
233 /* Recognize left precision. */
234 if (*fmt == '#')
236 if (!isdigit (*++fmt))
238 __set_errno (EINVAL);
239 return -1;
241 left_prec = to_digit (*fmt);
243 while (isdigit (*++fmt))
245 left_prec *= 10;
246 left_prec += to_digit (*fmt);
250 /* Recognize right precision. */
251 if (*fmt == '.')
253 if (!isdigit (*++fmt))
255 __set_errno (EINVAL);
256 return -1;
258 right_prec = to_digit (*fmt);
260 while (isdigit (*++fmt))
262 right_prec *= 10;
263 right_prec += to_digit (*fmt);
267 /* Handle modifier. This is an extension. */
268 if (*fmt == 'L')
270 ++fmt;
271 if (!__ldbl_is_dbl)
272 is_long_double = 1;
275 /* Handle format specifier. */
276 char int_symbol[4];
277 switch (*fmt++)
279 case 'i': { /* Use international currency symbol. */
280 const char *int_curr_symbol;
282 int_curr_symbol = _NL_CURRENT (LC_MONETARY, INT_CURR_SYMBOL);
283 strncpy(int_symbol, int_curr_symbol, 3);
284 int_symbol[3] = '\0';
286 currency_symbol_len = 3;
287 currency_symbol = &int_symbol[0];
288 space_char = int_curr_symbol[3];
289 int_format = 1;
290 break;
292 case 'n': /* Use national currency symbol. */
293 currency_symbol = _NL_CURRENT (LC_MONETARY, CURRENCY_SYMBOL);
294 currency_symbol_len = strlen (currency_symbol);
295 space_char = ' ';
296 int_format = 0;
297 break;
298 default: /* Any unrecognized format is an error. */
299 __set_errno (EINVAL);
300 return -1;
303 /* If not specified by the format string now find the values for
304 the format specification. */
305 if (p_sign_posn == -2)
306 p_sign_posn = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SIGN_POSN : P_SIGN_POSN);
307 if (n_sign_posn == -2)
308 n_sign_posn = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SIGN_POSN : N_SIGN_POSN);
310 if (right_prec == -1)
312 right_prec = *_NL_CURRENT (LC_MONETARY, int_format ? INT_FRAC_DIGITS : FRAC_DIGITS);
314 if (right_prec == '\377')
315 right_prec = 2;
318 /* If we have to print the digits grouped determine how many
319 extra characters this means. */
320 if (group && left_prec != -1)
321 left_prec += __guess_grouping (left_prec,
322 _NL_CURRENT (LC_MONETARY, MON_GROUPING));
324 /* Now it's time to get the value. */
325 if (is_long_double == 1)
327 fpnum.ldbl = va_arg (ap, long double);
328 is_negative = fpnum.ldbl < 0;
329 if (is_negative)
330 fpnum.ldbl = -fpnum.ldbl;
332 else
334 fpnum.dbl = va_arg (ap, double);
335 is_negative = fpnum.dbl < 0;
336 if (is_negative)
337 fpnum.dbl = -fpnum.dbl;
340 /* We now know the sign of the value and can determine the format. */
341 if (is_negative)
343 sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
344 /* If the locale does not specify a character for the
345 negative sign we use a '-'. */
346 if (*sign_string == '\0')
347 sign_string = (const char *) "-";
348 cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_CS_PRECEDES : N_CS_PRECEDES);
349 sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SEP_BY_SPACE : N_SEP_BY_SPACE);
350 sign_posn = n_sign_posn;
352 other_sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
353 other_cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_CS_PRECEDES : P_CS_PRECEDES);
354 other_sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SEP_BY_SPACE : P_SEP_BY_SPACE);
355 other_sign_posn = p_sign_posn;
357 else
359 sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
360 cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_CS_PRECEDES : P_CS_PRECEDES);
361 sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SEP_BY_SPACE : P_SEP_BY_SPACE);
362 sign_posn = p_sign_posn;
364 other_sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
365 if (*other_sign_string == '\0')
366 other_sign_string = (const char *) "-";
367 other_cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_CS_PRECEDES : N_CS_PRECEDES);
368 other_sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SEP_BY_SPACE : N_SEP_BY_SPACE);
369 other_sign_posn = n_sign_posn;
372 /* Set default values for unspecified information. */
373 if (cs_precedes != 0)
374 cs_precedes = 1;
375 if (other_cs_precedes != 0)
376 other_cs_precedes = 1;
377 if (sep_by_space == '\377')
378 sep_by_space = 0;
379 if (other_sep_by_space == '\377')
380 other_sep_by_space = 0;
381 if (sign_posn == '\377')
382 sign_posn = 1;
383 if (other_sign_posn == '\377')
384 other_sign_posn = 1;
386 /* Check for degenerate cases */
387 if (sep_by_space == 2)
389 if (sign_posn == 0 ||
390 (sign_posn == 1 && !cs_precedes) ||
391 (sign_posn == 2 && cs_precedes))
392 /* sign and symbol are not adjacent, so no separator */
393 sep_by_space = 0;
395 if (other_sep_by_space == 2)
397 if (other_sign_posn == 0 ||
398 (other_sign_posn == 1 && !other_cs_precedes) ||
399 (other_sign_posn == 2 && other_cs_precedes))
400 /* sign and symbol are not adjacent, so no separator */
401 other_sep_by_space = 0;
404 /* Set the left precision and padding needed for alignment */
405 if (left_prec == -1)
407 left_prec = 0;
408 left_pad = 0;
410 else
412 /* Set left_pad to number of spaces needed to align positive
413 and negative formats */
415 int left_bytes = 0;
416 int other_left_bytes = 0;
418 /* Work out number of bytes for currency string and separator
419 preceding the value */
420 if (cs_precedes)
422 left_bytes += currency_symbol_len;
423 if (sep_by_space != 0)
424 ++left_bytes;
427 if (other_cs_precedes)
429 other_left_bytes += currency_symbol_len;
430 if (other_sep_by_space != 0)
431 ++other_left_bytes;
434 /* Work out number of bytes for the sign (or left parenthesis)
435 preceding the value */
436 if (sign_posn == 0 && is_negative)
437 ++left_bytes;
438 else if (sign_posn == 1)
439 left_bytes += strlen (sign_string);
440 else if (cs_precedes && (sign_posn == 3 || sign_posn == 4))
441 left_bytes += strlen (sign_string);
443 if (other_sign_posn == 0 && !is_negative)
444 ++other_left_bytes;
445 else if (other_sign_posn == 1)
446 other_left_bytes += strlen (other_sign_string);
447 else if (other_cs_precedes &&
448 (other_sign_posn == 3 || other_sign_posn == 4))
449 other_left_bytes += strlen (other_sign_string);
451 /* Compare the number of bytes preceding the value for
452 each format, and set the padding accordingly */
453 if (other_left_bytes > left_bytes)
454 left_pad = other_left_bytes - left_bytes;
455 else
456 left_pad = 0;
459 /* Perhaps we'll someday make these things configurable so
460 better start using symbolic names now. */
461 #define left_paren '('
462 #define right_paren ')'
464 startp = dest; /* Remember start so we can compute length. */
466 while (left_pad-- > 0)
467 out_char (' ');
469 if (sign_posn == 0 && is_negative)
470 out_char (left_paren);
472 if (cs_precedes)
474 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 4
475 && sign_posn != 5)
477 out_string (sign_string);
478 if (sep_by_space == 2)
479 out_char (' ');
482 if (print_curr_symbol)
483 out_string (currency_symbol);
485 if (sign_posn == 4)
487 if (print_curr_symbol && sep_by_space == 2)
488 out_char (space_char);
489 out_string (sign_string);
490 if (sep_by_space == 1)
491 /* POSIX.2 and SUS are not clear on this case, but C99
492 says a space follows the adjacent-symbol-and-sign */
493 out_char (' ');
495 else
496 if (print_curr_symbol && sep_by_space == 1)
497 out_char (space_char);
499 else
500 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 3
501 && sign_posn != 4 && sign_posn != 5)
502 out_string (sign_string);
504 /* Print the number. */
505 #ifdef _IO_MTSAFE_IO
506 f._sbf._f._lock = NULL;
507 #endif
508 _IO_init_internal (&f._sbf._f, 0);
509 _IO_JUMPS (&f._sbf) = &_IO_str_jumps;
510 _IO_str_init_static_internal (&f, dest, (s + maxsize) - dest, dest);
511 /* We clear the last available byte so we can find out whether
512 the numeric representation is too long. */
513 s[maxsize - 1] = '\0';
515 memset (&info, '\0', sizeof (info));
516 info.prec = right_prec;
517 info.width = left_prec + (right_prec ? (right_prec + 1) : 0);
518 info.spec = 'f';
519 info.is_long_double = is_long_double;
520 info.group = group;
521 info.pad = pad;
522 info.extra = 1; /* This means use values from LC_MONETARY. */
524 ptr = &fpnum;
525 done = __printf_fp_l (&f._sbf._f, loc, &info, &ptr);
526 if (done < 0)
527 return -1;
529 if (s[maxsize - 1] != '\0')
531 __set_errno (E2BIG);
532 return -1;
535 dest += done;
537 if (!cs_precedes)
539 if (sign_posn == 3)
541 if (sep_by_space == 1)
542 out_char (' ');
543 out_string (sign_string);
546 if (print_curr_symbol)
548 if ((sign_posn == 3 && sep_by_space == 2)
549 || (sign_posn == 4 && sep_by_space == 1)
550 || (sign_posn == 2 && sep_by_space == 1)
551 || (sign_posn == 1 && sep_by_space == 1)
552 || (sign_posn == 0 && sep_by_space == 1))
553 out_char (space_char);
554 out_nstring (currency_symbol, currency_symbol_len);
557 if (sign_posn == 4)
559 if (sep_by_space == 2)
560 out_char (' ');
561 out_string (sign_string);
565 if (sign_posn == 2)
567 if (sep_by_space == 2)
568 out_char (' ');
569 out_string (sign_string);
572 if (sign_posn == 0 && is_negative)
573 out_char (right_paren);
575 /* Now test whether the output width is filled. */
576 if (dest - startp < width)
578 if (left)
579 /* We simply have to fill using spaces. */
581 out_char (' ');
582 while (dest - startp < width);
583 else
585 long int dist = width - (dest - startp);
586 for (char *cp = dest - 1; cp >= startp; --cp)
587 cp[dist] = cp[0];
589 dest += dist;
592 startp[--dist] = ' ';
593 while (dist > 0);
598 /* Terminate the string. */
599 *dest = '\0';
601 return dest - s;
604 ssize_t
605 ___strfmon_l (char *s, size_t maxsize, locale_t loc, const char *format, ...)
607 va_list ap;
609 va_start (ap, format);
611 ssize_t res = __vstrfmon_l (s, maxsize, loc, format, ap);
613 va_end (ap);
615 return res;
617 ldbl_strong_alias (___strfmon_l, __strfmon_l)
618 ldbl_weak_alias (___strfmon_l, strfmon_l)