source: trunk/src/helpers/nls.c@ 187

Last change on this file since 187 was 185, checked in by umoeller, 23 years ago

Third round of fixes for 0.9.19 (BAT files)

  • Property svn:eol-style set to CRLF
  • Property svn:keywords set to Author Date Id Revision
File size: 21.8 KB
Line 
1
2/*
3 *@@sourcefile nls.c:
4 * contains a few helpers for National Language Support (NLS),
5 * such as printing strings with the format specified by
6 * the "Country" object.
7 *
8 * Usage: All OS/2 programs.
9 *
10 * Function prefixes (new with V0.81):
11 * -- nls* NLS helpers
12 *
13 * This file is new with 0.9.16, but contains functions
14 * formerly in stringh.c.
15 *
16 * Note: Version numbering in this file relates to XWorkplace version
17 * numbering.
18 *
19 *@@header "helpers\nls.h"
20 *@@added V0.9.16 (2001-10-11) [umoeller]
21 */
22
23/*
24 * Copyright (C) 1997-2002 Ulrich M”ller.
25 * This file is part of the "XWorkplace helpers" source package.
26 * This is free software; you can redistribute it and/or modify
27 * it under the terms of the GNU General Public License as published
28 * by the Free Software Foundation, in version 2 as it comes in the
29 * "COPYING" file of the XWorkplace main distribution.
30 * This program is distributed in the hope that it will be useful,
31 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 * GNU General Public License for more details.
34 */
35
36#define OS2EMX_PLAIN_CHAR
37 // this is needed for "os2emx.h"; if this is defined,
38 // emx will define PSZ as _signed_ char, otherwise
39 // as unsigned char
40
41#define INCL_DOSNLS
42#define INCL_DOSDATETIME
43#define INCL_DOSERRORS
44#define INCL_WINSHELLDATA
45#include <os2.h>
46
47#include <stdlib.h>
48#include <stdio.h>
49#include <string.h>
50#include <math.h>
51
52// XWP's setup.h replaces strchr and the like, and
53// we want the originals in here
54#define DONT_REPLACE_FOR_DBCS
55#include "setup.h" // code generation and debugging options
56
57#include "helpers\nls.h"
58#include "helpers\prfh.h"
59#include "helpers\standards.h"
60
61#pragma hdrstop
62
63/*
64 *@@category: Helpers\National Language Support
65 * See nls.c.
66 */
67
68/* ******************************************************************
69 *
70 * DBCS support
71 *
72 ********************************************************************/
73
74#define MAX_LEADBYTE 256
75
76#pragma pack(1)
77
78typedef struct _DBCSVECTOR
79{
80 BYTE bLow;
81 BYTE bHigh;
82} DBCSVECTOR;
83
84#pragma pack()
85
86BOOL G_afLeadByte[MAX_LEADBYTE] = {0};
87ULONG G_fDBCS = 2; // not queried yet
88COUNTRYCODE G_cc = { 0, 0 };
89DBCSVECTOR G_aDBCSVector[8];
90
91/*
92 *@@ nlsDBCS:
93 * returns TRUE if the system is currently using DBCS.
94 *
95 *@@added V0.9.19 (2002-06-13) [umoeller]
96 *@@changed V0.9.20 (2002-07-03) [umoeller]: fixed, this never worked
97 */
98
99BOOL nlsDBCS(VOID)
100{
101 APIRET arc;
102
103 if (G_fDBCS != 2)
104 // already queried:
105 return G_fDBCS;
106
107 // V0.9.20 (2002-07-03) [umoeller]
108 // assume a non-DBCS system UNLESS the below
109 // loop gives us something meaningful; even
110 // on non-DBCS systems like mine, DosQueryDBCSEnv
111 // does not return an error
112 G_fDBCS = FALSE;
113
114 if (!(arc = DosQueryDBCSEnv(8 * sizeof(DBCSVECTOR),
115 &G_cc,
116 (PCHAR)G_aDBCSVector)))
117 {
118 int i;
119 for (i = 0;
120 i < 8;
121 ++i)
122 {
123 if ( (G_aDBCSVector[i].bLow)
124 && (G_aDBCSVector[i].bHigh)
125 )
126 {
127 int n;
128 for (n = G_aDBCSVector[i].bLow;
129 n <= G_aDBCSVector[i].bHigh;
130 ++n)
131 G_afLeadByte[n] = TRUE;
132 G_fDBCS = TRUE;
133 }
134 else
135 break;
136 }
137 }
138
139 return G_fDBCS;
140}
141
142/*
143 *@@ nlsQueryDBCSChar:
144 * returns the type of the DBCS character with
145 * the given index. Note that the index is the
146 * DBCS character index, not just the array
147 * index into the CHAR array.
148 *
149 * Returns:
150 *
151 * -- TYPE_SBCS: ulOfs is single byte.
152 *
153 * -- TYPE_DBCS_1ST: ulOfs is a double-byte lead char.
154 *
155 * -- TYPE_DBCS_2ND: ulOfs is a double-byte trail char.
156 *
157 * Preconditions:
158 *
159 * -- nlsDBCS must have been called to initialize our
160 * globals, and must have returned TRUE.
161 *
162 *@@added V0.9.19 (2002-06-13) [umoeller]
163 */
164
165ULONG nlsQueryDBCSChar(PCSZ pcszString,
166 ULONG ulOfs)
167
168{
169 ULONG ulDBCSType = TYPE_SBCS;
170 ULONG i;
171
172 for (i = 0;
173 i <= ulOfs;
174 ++i)
175 {
176 switch (ulDBCSType)
177 {
178 case TYPE_SBCS:
179 case TYPE_DBCS_2ND:
180 ulDBCSType = G_afLeadByte[pcszString[i]];
181 break;
182
183 case TYPE_DBCS_1ST :
184 ulDBCSType = TYPE_DBCS_2ND;
185 break;
186 }
187 }
188
189 return ulDBCSType;
190}
191
192/*
193 *@@ nlschr:
194 * replacement for strchr with DBCS support.
195 *
196 * If the system is not running with DBCS,
197 * this calls plain strchr automatically.
198 *
199 *@@added V0.9.19 (2002-06-13) [umoeller]
200 */
201
202PSZ nlschr(PCSZ p, char c)
203{
204 PCSZ p2;
205 ULONG ulDBCS;
206
207 if (!nlsDBCS())
208 // not DBCS:
209 return strchr(p, c);
210
211 // we're on DBCS:
212 for (p2 = p;
213 *p2;
214 ++p2)
215 {
216 if (*p2 == c)
217 {
218 // match: return this only if it's SBCS;
219 // if it's a DBCS lead char, skip it
220 switch (ulDBCS = nlsQueryDBCSChar(p, p2 - p))
221 {
222 case TYPE_SBCS:
223 return (PSZ)p2;
224
225 case TYPE_DBCS_1ST:
226 ++p2;
227 }
228 }
229 }
230
231 return NULL;
232}
233
234/*
235 *@@ nlsrchr:
236 * replacement for strrchr with DBCS support.
237 *
238 * If the system is not running with DBCS,
239 * this calls plain strrchr automatically.
240 *
241 *@@added V0.9.19 (2002-06-13) [umoeller]
242 */
243
244PSZ nlsrchr(PCSZ p, char c)
245{
246 PCSZ p2;
247 ULONG ulDBCS,
248 ulLength;
249
250 if (!nlsDBCS())
251 // not DBCS:
252 return strrchr(p, c);
253
254 // we're on DBCS:
255 ulLength = strlen(p);
256 for (p2 = p + ulLength - 1;
257 p2 >= p;
258 --p2)
259 {
260 if (*p2 == c)
261 {
262 // match: return this only if it's SBCS;
263 // if it's a DBCS trail char, skip it
264 switch (ulDBCS = nlsQueryDBCSChar(p, p2 - p))
265 {
266 case TYPE_SBCS:
267 return (PSZ)p2;
268
269 case TYPE_DBCS_2ND:
270 --p2;
271 }
272 }
273 }
274
275 return NULL;
276}
277
278/* ******************************************************************
279 *
280 * Country-dependent formatting
281 *
282 ********************************************************************/
283
284/*
285 *@@ nlsQueryCountrySettings:
286 * this returns the most frequently used country settings
287 * all at once into a COUNTRYSETTINGS structure (prfh.h).
288 * This data corresponds to the user settings in the
289 * WPS "Country" object (which writes the data in "PM_National"
290 * in OS2.INI).
291 *
292 * In case a key cannot be found, the following (English)
293 * default values are set:
294 * -- ulDateFormat = 0 (English date format, mm.dd.yyyy);
295 * -- ulTimeFormat = 0 (12-hour clock);
296 * -- cDateSep = '/' (date separator);
297 * -- cTimeSep = ':' (time separator);
298 * -- cDecimal = '.' (decimal separator).
299 * -- cThousands = ',' (thousands separator).
300 *
301 *@@added V0.9.0 [umoeller]
302 *@@changed V0.9.7 (2000-12-02) [umoeller]: added cDecimal
303 */
304
305VOID nlsQueryCountrySettings(PCOUNTRYSETTINGS pcs)
306{
307 if (pcs)
308 {
309 pcs->ulDateFormat = PrfQueryProfileInt(HINI_USER,
310 (PSZ)PMINIAPP_NATIONAL,
311 "iDate",
312 0);
313 pcs->ulTimeFormat = PrfQueryProfileInt(HINI_USER,
314 (PSZ)PMINIAPP_NATIONAL,
315 "iTime",
316 0);
317 pcs->cDateSep = prfhQueryProfileChar(HINI_USER,
318 (PSZ)PMINIAPP_NATIONAL,
319 "sDate",
320 '/');
321 pcs->cTimeSep = prfhQueryProfileChar(HINI_USER,
322 (PSZ)PMINIAPP_NATIONAL,
323 "sTime",
324 ':');
325 pcs->cDecimal = prfhQueryProfileChar(HINI_USER,
326 (PSZ)PMINIAPP_NATIONAL,
327 "sDecimal",
328 '.');
329 pcs->cThousands = prfhQueryProfileChar(HINI_USER,
330 (PSZ)PMINIAPP_NATIONAL,
331 "sThousand",
332 ',');
333 }
334}
335
336/*
337 *@@ nlsThousandsULong:
338 * converts a ULONG into a decimal string, while
339 * inserting thousands separators into it. Specify
340 * the separator character in cThousands.
341 *
342 * Returns pszTarget so you can use it directly
343 * with sprintf and the "%s" flag.
344 *
345 * For cThousands, you should use the data in
346 * OS2.INI ("PM_National" application), which is
347 * always set according to the "Country" object.
348 * You can use nlsQueryCountrySettings to
349 * retrieve this setting.
350 *
351 * Use nlsThousandsDouble for "double" values.
352 *
353 *@@changed V0.9.20 (2002-07-03) [umoeller]: optimized
354 */
355
356PSZ nlsThousandsULong(PSZ pszTarget, // out: decimal as string
357 ULONG ul, // in: decimal to convert
358 CHAR cThousands) // in: separator char (e.g. '.')
359{
360 USHORT ust, uss, usc;
361 CHAR szTemp[40];
362 usc = sprintf(szTemp, "%lu", ul); // V0.9.20 (2002-07-03) [umoeller]
363
364 ust = 0;
365 // usc = strlen(szTemp);
366 for (uss = 0; uss < usc; uss++)
367 {
368 if (uss)
369 if (((usc - uss) % 3) == 0)
370 {
371 pszTarget[ust] = cThousands;
372 ust++;
373 }
374 pszTarget[ust] = szTemp[uss];
375 ust++;
376 }
377 pszTarget[ust] = '\0';
378
379 return pszTarget;
380}
381
382/*
383 * strhThousandsULong:
384 * wrapper around nlsThousandsULong for those
385 * who used the XFLDR.DLL export.
386 *
387 *added V0.9.16 (2001-10-11) [umoeller]
388 */
389
390PSZ APIENTRY strhThousandsULong(PSZ pszTarget, // out: decimal as string
391 ULONG ul, // in: decimal to convert
392 CHAR cThousands) // in: separator char (e.g. '.')
393{
394 return nlsThousandsULong(pszTarget, ul, cThousands);
395}
396
397/*
398 *@@ nlsThousandsDouble:
399 * like nlsThousandsULong, but for a "double"
400 * value. Note that after-comma values are truncated.
401 *
402 *@@changed V0.9.20 (2002-07-03) [umoeller]: optimized
403 */
404
405PSZ nlsThousandsDouble(PSZ pszTarget,
406 double dbl,
407 CHAR cThousands)
408{
409 USHORT ust, uss, usc;
410 CHAR szTemp[40];
411 usc = sprintf(szTemp, "%.0f", floor(dbl)); // V0.9.20 (2002-07-03) [umoeller]
412
413 ust = 0;
414 // usc = strlen(szTemp);
415 for (uss = 0; uss < usc; uss++)
416 {
417 if (uss)
418 if (((usc - uss) % 3) == 0)
419 {
420 pszTarget[ust] = cThousands;
421 ust++;
422 }
423 pszTarget[ust] = szTemp[uss];
424 ust++;
425 }
426 pszTarget[ust] = '\0';
427
428 return pszTarget;
429}
430
431/*
432 *@@ nlsVariableDouble:
433 * like nlsThousandsULong, but for a "double" value, and
434 * with a variable number of decimal places depending on the
435 * size of the quantity.
436 *
437 *@@added V0.9.6 (2000-11-12) [pr]
438 *@@changed V0.9.20 (2002-07-03) [umoeller]: now using PCSZ pcszUnits
439 */
440
441PSZ nlsVariableDouble(PSZ pszTarget,
442 double dbl,
443 PCSZ pcszUnits,
444 CHAR cThousands)
445{
446 if (dbl < 100.0)
447 sprintf(pszTarget, "%.2f%s", dbl, pcszUnits);
448 else
449 if (dbl < 1000.0)
450 sprintf(pszTarget, "%.1f%s", dbl, pcszUnits);
451 else
452 strcat(nlsThousandsDouble(pszTarget, dbl, cThousands),
453 pcszUnits);
454
455 return pszTarget;
456}
457
458/*
459 *@@ nlsFileDate:
460 * converts file date data to a string (to pszBuf).
461 * You can pass any FDATE structure to this function,
462 * which are returned in those FILEFINDBUF* or
463 * FILESTATUS* structs by the Dos* functions.
464 *
465 * ulDateFormat is the PM setting for the date format,
466 * as set in the "Country" object, and can be queried using
467 + PrfQueryProfileInt(HINI_USER, "PM_National", "iDate", 0);
468 *
469 * meaning:
470 * -- 0 mm.dd.yyyy (English)
471 * -- 1 dd.mm.yyyy (e.g. German)
472 * -- 2 yyyy.mm.dd (Japanese, ISO)
473 * -- 3 yyyy.dd.mm
474 *
475 * cDateSep is used as a date separator (e.g. '.').
476 * This can be queried using:
477 + prfhQueryProfileChar(HINI_USER, "PM_National", "sDate", '/');
478 *
479 * Alternatively, you can query all the country settings
480 * at once using nlsQueryCountrySettings (prfh.c).
481 *
482 *@@changed V0.9.0 (99-11-07) [umoeller]: now calling nlsDateTime
483 */
484
485VOID nlsFileDate(PSZ pszBuf, // out: string returned
486 FDATE *pfDate, // in: date information
487 ULONG ulDateFormat, // in: date format (0-3)
488 CHAR cDateSep) // in: date separator (e.g. '.')
489{
490 DATETIME dt;
491 dt.day = pfDate->day;
492 dt.month = pfDate->month;
493 dt.year = pfDate->year + 1980;
494
495 nlsDateTime(pszBuf,
496 NULL, // no time
497 &dt,
498 ulDateFormat,
499 cDateSep,
500 0, 0); // no time
501}
502
503/*
504 *@@ nlsFileTime:
505 * converts file time data to a string (to pszBuf).
506 * You can pass any FTIME structure to this function,
507 * which are returned in those FILEFINDBUF* or
508 * FILESTATUS* structs by the Dos* functions.
509 *
510 * ulTimeFormat is the PM setting for the time format,
511 * as set in the "Country" object, and can be queried using
512 + PrfQueryProfileInt(HINI_USER, "PM_National", "iTime", 0);
513 * meaning:
514 * -- 0 12-hour clock
515 * -- >0 24-hour clock
516 *
517 * cDateSep is used as a time separator (e.g. ':').
518 * This can be queried using:
519 + prfhQueryProfileChar(HINI_USER, "PM_National", "sTime", ':');
520 *
521 * Alternatively, you can query all the country settings
522 * at once using nlsQueryCountrySettings (prfh.c).
523 *
524 *@@changed V0.8.5 (99-03-15) [umoeller]: fixed 12-hour crash
525 *@@changed V0.9.0 (99-11-07) [umoeller]: now calling nlsDateTime
526 */
527
528VOID nlsFileTime(PSZ pszBuf, // out: string returned
529 FTIME *pfTime, // in: time information
530 ULONG ulTimeFormat, // in: 24-hour time format (0 or 1)
531 CHAR cTimeSep) // in: time separator (e.g. ':')
532{
533 DATETIME dt;
534 dt.hours = pfTime->hours;
535 dt.minutes = pfTime->minutes;
536 dt.seconds = pfTime->twosecs * 2;
537
538 nlsDateTime(NULL, // no date
539 pszBuf,
540 &dt,
541 0, 0, // no date
542 ulTimeFormat,
543 cTimeSep);
544}
545
546/*
547 *@@ nlsDateTime:
548 * converts Control Program DATETIME info
549 * into two strings. See nlsFileDate and nlsFileTime
550 * for more detailed parameter descriptions.
551 *
552 *@@added V0.9.0 (99-11-07) [umoeller]
553 *@@changed V0.9.16 (2001-12-05) [pr]: fixed AM/PM hour bug
554 *@@changed V0.9.18 (2002-02-13) [umoeller]: fixed AM/PM hour bug fix
555 */
556
557VOID nlsDateTime(PSZ pszDate, // out: date string returned (can be NULL)
558 PSZ pszTime, // out: time string returned (can be NULL)
559 DATETIME *pDateTime, // in: date/time information
560 ULONG ulDateFormat, // in: date format (0-3); see nlsFileDate
561 CHAR cDateSep, // in: date separator (e.g. '.')
562 ULONG ulTimeFormat, // in: 24-hour time format (0 or 1); see nlsFileTime
563 CHAR cTimeSep) // in: time separator (e.g. ':')
564{
565 if (pszDate)
566 {
567 switch (ulDateFormat)
568 {
569 case 0: // mm.dd.yyyy (English)
570 sprintf(pszDate, "%02d%c%02d%c%04d",
571 pDateTime->month,
572 cDateSep,
573 pDateTime->day,
574 cDateSep,
575 pDateTime->year);
576 break;
577
578 case 1: // dd.mm.yyyy (e.g. German)
579 sprintf(pszDate, "%02d%c%02d%c%04d",
580 pDateTime->day,
581 cDateSep,
582 pDateTime->month,
583 cDateSep,
584 pDateTime->year);
585 break;
586
587 case 2: // yyyy.mm.dd (Japanese)
588 sprintf(pszDate, "%04d%c%02d%c%02d",
589 pDateTime->year,
590 cDateSep,
591 pDateTime->month,
592 cDateSep,
593 pDateTime->day);
594 break;
595
596 default: // yyyy.dd.mm
597 sprintf(pszDate, "%04d%c%02d%c%02d",
598 pDateTime->year,
599 cDateSep,
600 pDateTime->day,
601 cDateSep,
602 pDateTime->month);
603 break;
604 }
605 }
606
607 if (pszTime)
608 {
609 if (ulTimeFormat == 0)
610 {
611 // for 12-hour clock, we need additional INI data
612 CHAR szAMPM[10] = "err";
613
614 if (pDateTime->hours >= 12) // V0.9.16 (2001-12-05) [pr] if (pDateTime->hours > 12)
615 {
616 // yeah cool Paul, now we get 00:20 PM if it's 20 past noon
617 // V0.9.18 (2002-02-13) [umoeller]
618 ULONG ulHours;
619 if (!(ulHours = pDateTime->hours % 12))
620 ulHours = 12;
621
622 // >= 12h: PM.
623 PrfQueryProfileString(HINI_USER,
624 "PM_National",
625 "s2359", // key
626 "PM", // default
627 szAMPM, sizeof(szAMPM)-1);
628 sprintf(pszTime, "%02d%c%02d%c%02d %s",
629 // leave 12 == 12 (not 0)
630 ulHours,
631 cTimeSep,
632 pDateTime->minutes,
633 cTimeSep,
634 pDateTime->seconds,
635 szAMPM);
636 }
637 else
638 {
639 // < 12h: AM
640 PrfQueryProfileString(HINI_USER,
641 "PM_National",
642 "s1159", // key
643 "AM", // default
644 szAMPM, sizeof(szAMPM)-1);
645 sprintf(pszTime, "%02d%c%02d%c%02d %s",
646 pDateTime->hours,
647 cTimeSep,
648 pDateTime->minutes,
649 cTimeSep,
650 pDateTime->seconds,
651 szAMPM);
652 }
653 }
654 else
655 // 24-hour clock
656 sprintf(pszTime, "%02d%c%02d%c%02d",
657 pDateTime->hours,
658 cTimeSep,
659 pDateTime->minutes,
660 cTimeSep,
661 pDateTime->seconds);
662 }
663}
664
665/*
666 * strhDateTime:
667 * wrapper around nlsDateTime for those who used
668 * the XFLDR.DLL export.
669 */
670
671VOID APIENTRY strhDateTime(PSZ pszDate, // out: date string returned (can be NULL)
672 PSZ pszTime, // out: time string returned (can be NULL)
673 DATETIME *pDateTime, // in: date/time information
674 ULONG ulDateFormat, // in: date format (0-3); see nlsFileDate
675 CHAR cDateSep, // in: date separator (e.g. '.')
676 ULONG ulTimeFormat, // in: 24-hour time format (0 or 1); see nlsFileTime
677 CHAR cTimeSep) // in: time separator (e.g. ':')
678{
679 nlsDateTime(pszDate,
680 pszTime,
681 pDateTime,
682 ulDateFormat,
683 cDateSep,
684 ulTimeFormat,
685 cTimeSep);
686}
687
688
689/*
690 *@@ nlsUpper:
691 * quick hack for upper-casing a string.
692 *
693 * This uses DosMapCase with the default system country
694 * code and the process's codepage. WARNING: DosMapCase
695 * is a 16-bit API and therefore quite slow. Use this
696 * with care.
697 *
698 *@@added V0.9.16 (2001-10-25) [umoeller]
699 */
700
701APIRET nlsUpper(PSZ psz, // in/out: string
702 ULONG ulLength) // in: string length; if 0, we run strlen(psz)
703{
704 COUNTRYCODE cc;
705
706 if (psz)
707 {
708 if (!ulLength)
709 ulLength = strlen(psz);
710
711 if (ulLength)
712 {
713 cc.country = 0; // use system country code
714 cc.codepage = 0; // use process default codepage
715 return DosMapCase(ulLength,
716 &cc,
717 psz);
718 }
719 }
720
721 return ERROR_INVALID_PARAMETER;
722}
723
724
Note: See TracBrowser for help on using the repository browser.