1 /* nl_langinfo() replacement: query locale dependent information.
2 
3    Copyright (C) 2007-2020 Free Software Foundation, Inc.
4 
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 3 of the License, or
8    (at your option) any later version.
9 
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
17 
18 #include <config.h>
19 
20 /* Specification.  */
21 #include <langinfo.h>
22 
23 #include <locale.h>
24 #include <string.h>
25 #if defined _WIN32 && ! defined __CYGWIN__
26 # define WIN32_LEAN_AND_MEAN  /* avoid including junk */
27 # include <windows.h>
28 # include <stdio.h>
29 #endif
30 
31 /* nl_langinfo() must be multithread-safe.  To achieve this without using
32    thread-local storage:
33      1. We use a specific static buffer for each possible argument.
34         So that different threads can call nl_langinfo with different arguments,
35         without interfering.
36      2. We use a simple strcpy or memcpy to fill this static buffer.  Filling it
37         through, for example, strcpy + strcat would not be guaranteed to leave
38         the buffer's contents intact if another thread is currently accessing
39         it.  If necessary, the contents is first assembled in a stack-allocated
40         buffer.  */
41 
42 #if !REPLACE_NL_LANGINFO || GNULIB_defined_CODESET
43 /* Return the codeset of the current locale, if this is easily deducible.
44    Otherwise, return "".  */
45 static char *
ctype_codeset(void)46 ctype_codeset (void)
47 {
48   static char result[2 + 10 + 1];
49   char buf[2 + 10 + 1];
50   char locale[SETLOCALE_NULL_MAX];
51   char *codeset;
52   size_t codesetlen;
53 
54   if (setlocale_null_r (LC_CTYPE, locale, sizeof (locale)))
55     locale[0] = '\0';
56 
57   codeset = buf;
58   codeset[0] = '\0';
59 
60   if (locale && locale[0])
61     {
62       /* If the locale name contains an encoding after the dot, return it.  */
63       char *dot = strchr (locale, '.');
64 
65       if (dot)
66         {
67           /* Look for the possible @... trailer and remove it, if any.  */
68           char *codeset_start = dot + 1;
69           char const *modifier = strchr (codeset_start, '@');
70 
71           if (! modifier)
72             codeset = codeset_start;
73           else
74             {
75               codesetlen = modifier - codeset_start;
76               if (codesetlen < sizeof buf)
77                 {
78                   codeset = memcpy (buf, codeset_start, codesetlen);
79                   codeset[codesetlen] = '\0';
80                 }
81             }
82         }
83     }
84 
85 # if defined _WIN32 && ! defined __CYGWIN__
86   /* If setlocale is successful, it returns the number of the
87      codepage, as a string.  Otherwise, fall back on Windows API
88      GetACP, which returns the locale's codepage as a number (although
89      this doesn't change according to what the 'setlocale' call specified).
90      Either way, prepend "CP" to make it a valid codeset name.  */
91   codesetlen = strlen (codeset);
92   if (0 < codesetlen && codesetlen < sizeof buf - 2)
93     memmove (buf + 2, codeset, codesetlen + 1);
94   else
95     sprintf (buf + 2, "%u", GetACP ());
96   /* For a locale name such as "French_France.65001", in Windows 10,
97      setlocale now returns "French_France.utf8" instead.  */
98   if (strcmp (buf + 2, "65001") == 0 || strcmp (buf + 2, "utf8") == 0)
99     return (char *) "UTF-8";
100   else
101     {
102       memcpy (buf, "CP", 2);
103       strcpy (result, buf);
104       return result;
105     }
106 # else
107   strcpy (result, codeset);
108   return result;
109 #endif
110 }
111 #endif
112 
113 
114 #if REPLACE_NL_LANGINFO
115 
116 /* Override nl_langinfo with support for added nl_item values.  */
117 
118 # undef nl_langinfo
119 
120 char *
rpl_nl_langinfo(nl_item item)121 rpl_nl_langinfo (nl_item item)
122 {
123   switch (item)
124     {
125 # if GNULIB_defined_CODESET
126     case CODESET:
127       return ctype_codeset ();
128 # endif
129 # if GNULIB_defined_T_FMT_AMPM
130     case T_FMT_AMPM:
131       return (char *) "%I:%M:%S %p";
132 # endif
133 # if GNULIB_defined_ALTMON
134     case ALTMON_1:
135     case ALTMON_2:
136     case ALTMON_3:
137     case ALTMON_4:
138     case ALTMON_5:
139     case ALTMON_6:
140     case ALTMON_7:
141     case ALTMON_8:
142     case ALTMON_9:
143     case ALTMON_10:
144     case ALTMON_11:
145     case ALTMON_12:
146       /* We don't ship the appropriate localizations with gnulib.  Therefore,
147          treat ALTMON_i like MON_i.  */
148       item = item - ALTMON_1 + MON_1;
149       break;
150 # endif
151 # if GNULIB_defined_ERA
152     case ERA:
153       /* The format is not standardized.  In glibc it is a sequence of strings
154          of the form "direction:offset:start_date:end_date:era_name:era_format"
155          with an empty string at the end.  */
156       return (char *) "";
157     case ERA_D_FMT:
158       /* The %Ex conversion in strftime behaves like %x if the locale does not
159          have an alternative time format.  */
160       item = D_FMT;
161       break;
162     case ERA_D_T_FMT:
163       /* The %Ec conversion in strftime behaves like %c if the locale does not
164          have an alternative time format.  */
165       item = D_T_FMT;
166       break;
167     case ERA_T_FMT:
168       /* The %EX conversion in strftime behaves like %X if the locale does not
169          have an alternative time format.  */
170       item = T_FMT;
171       break;
172     case ALT_DIGITS:
173       /* The format is not standardized.  In glibc it is a sequence of 10
174          strings, appended in memory.  */
175       return (char *) "\0\0\0\0\0\0\0\0\0\0";
176 # endif
177 # if GNULIB_defined_YESEXPR || !FUNC_NL_LANGINFO_YESEXPR_WORKS
178     case YESEXPR:
179       return (char *) "^[yY]";
180     case NOEXPR:
181       return (char *) "^[nN]";
182 # endif
183     default:
184       break;
185     }
186   return nl_langinfo (item);
187 }
188 
189 #else
190 
191 /* Provide nl_langinfo from scratch, either for native MS-Windows, or
192    for old Unix platforms without locales, such as Linux libc5 or
193    BeOS.  */
194 
195 # include <time.h>
196 
197 char *
nl_langinfo(nl_item item)198 nl_langinfo (nl_item item)
199 {
200   char buf[100];
201   struct tm tmm = { 0 };
202 
203   switch (item)
204     {
205     /* nl_langinfo items of the LC_CTYPE category */
206     case CODESET:
207       {
208         char *codeset = ctype_codeset ();
209         if (*codeset)
210           return codeset;
211       }
212 # ifdef __BEOS__
213       return (char *) "UTF-8";
214 # else
215       return (char *) "ISO-8859-1";
216 # endif
217     /* nl_langinfo items of the LC_NUMERIC category */
218     case RADIXCHAR:
219       return localeconv () ->decimal_point;
220     case THOUSEP:
221       return localeconv () ->thousands_sep;
222 # ifdef GROUPING
223     case GROUPING:
224       return localeconv () ->grouping;
225 # endif
226     /* nl_langinfo items of the LC_TIME category.
227        TODO: Really use the locale.  */
228     case D_T_FMT:
229     case ERA_D_T_FMT:
230       return (char *) "%a %b %e %H:%M:%S %Y";
231     case D_FMT:
232     case ERA_D_FMT:
233       return (char *) "%m/%d/%y";
234     case T_FMT:
235     case ERA_T_FMT:
236       return (char *) "%H:%M:%S";
237     case T_FMT_AMPM:
238       return (char *) "%I:%M:%S %p";
239     case AM_STR:
240       {
241         static char result[80];
242         if (!strftime (buf, sizeof result, "%p", &tmm))
243           return (char *) "AM";
244         strcpy (result, buf);
245         return result;
246       }
247     case PM_STR:
248       {
249         static char result[80];
250         tmm.tm_hour = 12;
251         if (!strftime (buf, sizeof result, "%p", &tmm))
252           return (char *) "PM";
253         strcpy (result, buf);
254         return result;
255       }
256     case DAY_1:
257     case DAY_2:
258     case DAY_3:
259     case DAY_4:
260     case DAY_5:
261     case DAY_6:
262     case DAY_7:
263       {
264         static char result[7][50];
265         static char const days[][sizeof "Wednesday"] = {
266           "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
267           "Friday", "Saturday"
268         };
269         tmm.tm_wday = item - DAY_1;
270         if (!strftime (buf, sizeof result[0], "%A", &tmm))
271           return (char *) days[item - DAY_1];
272         strcpy (result[item - DAY_1], buf);
273         return result[item - DAY_1];
274       }
275     case ABDAY_1:
276     case ABDAY_2:
277     case ABDAY_3:
278     case ABDAY_4:
279     case ABDAY_5:
280     case ABDAY_6:
281     case ABDAY_7:
282       {
283         static char result[7][30];
284         static char const abdays[][sizeof "Sun"] = {
285           "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
286         };
287         tmm.tm_wday = item - ABDAY_1;
288         if (!strftime (buf, sizeof result[0], "%a", &tmm))
289           return (char *) abdays[item - ABDAY_1];
290         strcpy (result[item - ABDAY_1], buf);
291         return result[item - ABDAY_1];
292       }
293     {
294       static char const months[][sizeof "September"] = {
295         "January", "February", "March", "April", "May", "June", "July",
296         "September", "October", "November", "December"
297       };
298       case MON_1:
299       case MON_2:
300       case MON_3:
301       case MON_4:
302       case MON_5:
303       case MON_6:
304       case MON_7:
305       case MON_8:
306       case MON_9:
307       case MON_10:
308       case MON_11:
309       case MON_12:
310         {
311           static char result[12][50];
312           tmm.tm_mon = item - MON_1;
313           if (!strftime (buf, sizeof result[0], "%B", &tmm))
314             return (char *) months[item - MON_1];
315           strcpy (result[item - MON_1], buf);
316           return result[item - MON_1];
317         }
318       case ALTMON_1:
319       case ALTMON_2:
320       case ALTMON_3:
321       case ALTMON_4:
322       case ALTMON_5:
323       case ALTMON_6:
324       case ALTMON_7:
325       case ALTMON_8:
326       case ALTMON_9:
327       case ALTMON_10:
328       case ALTMON_11:
329       case ALTMON_12:
330         {
331           static char result[12][50];
332           tmm.tm_mon = item - ALTMON_1;
333           /* The platforms without nl_langinfo() don't support strftime with
334              %OB.  We don't even need to try.  */
335           #if 0
336           if (!strftime (buf, sizeof result[0], "%OB", &tmm))
337           #endif
338             if (!strftime (buf, sizeof result[0], "%B", &tmm))
339               return (char *) months[item - ALTMON_1];
340           strcpy (result[item - ALTMON_1], buf);
341           return result[item - ALTMON_1];
342         }
343     }
344     case ABMON_1:
345     case ABMON_2:
346     case ABMON_3:
347     case ABMON_4:
348     case ABMON_5:
349     case ABMON_6:
350     case ABMON_7:
351     case ABMON_8:
352     case ABMON_9:
353     case ABMON_10:
354     case ABMON_11:
355     case ABMON_12:
356       {
357         static char result[12][30];
358         static char const abmonths[][sizeof "Jan"] = {
359           "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
360           "Sep", "Oct", "Nov", "Dec"
361         };
362         tmm.tm_mon = item - ABMON_1;
363         if (!strftime (buf, sizeof result[0], "%b", &tmm))
364           return (char *) abmonths[item - ABMON_1];
365         strcpy (result[item - ABMON_1], buf);
366         return result[item - ABMON_1];
367       }
368     case ERA:
369       return (char *) "";
370     case ALT_DIGITS:
371       return (char *) "\0\0\0\0\0\0\0\0\0\0";
372     /* nl_langinfo items of the LC_MONETARY category.  */
373     case CRNCYSTR:
374       return localeconv () ->currency_symbol;
375 # ifdef INT_CURR_SYMBOL
376     case INT_CURR_SYMBOL:
377       return localeconv () ->int_curr_symbol;
378     case MON_DECIMAL_POINT:
379       return localeconv () ->mon_decimal_point;
380     case MON_THOUSANDS_SEP:
381       return localeconv () ->mon_thousands_sep;
382     case MON_GROUPING:
383       return localeconv () ->mon_grouping;
384     case POSITIVE_SIGN:
385       return localeconv () ->positive_sign;
386     case NEGATIVE_SIGN:
387       return localeconv () ->negative_sign;
388     case FRAC_DIGITS:
389       return & localeconv () ->frac_digits;
390     case INT_FRAC_DIGITS:
391       return & localeconv () ->int_frac_digits;
392     case P_CS_PRECEDES:
393       return & localeconv () ->p_cs_precedes;
394     case N_CS_PRECEDES:
395       return & localeconv () ->n_cs_precedes;
396     case P_SEP_BY_SPACE:
397       return & localeconv () ->p_sep_by_space;
398     case N_SEP_BY_SPACE:
399       return & localeconv () ->n_sep_by_space;
400     case P_SIGN_POSN:
401       return & localeconv () ->p_sign_posn;
402     case N_SIGN_POSN:
403       return & localeconv () ->n_sign_posn;
404 # endif
405     /* nl_langinfo items of the LC_MESSAGES category
406        TODO: Really use the locale. */
407     case YESEXPR:
408       return (char *) "^[yY]";
409     case NOEXPR:
410       return (char *) "^[nN]";
411     default:
412       return (char *) "";
413     }
414 }
415 
416 #endif
417