clocale.pp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. {
  2. This file is part of the Free Pascal run time library.
  3. Copyright (c) 2008 by the Free Pascal development team.
  4. Init rtl formating variables based on libc locales
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. { Initial implementation by petr kristan }
  12. unit clocale;
  13. {$mode objfpc}
  14. interface
  15. {$ifdef localedebug}
  16. // for easier debugging, allows to print untransformed values in test
  17. Type TOrgFormatSettings = record
  18. ShortDateFormat,
  19. LongDateFormat ,
  20. ShortTimeFormat,
  21. LongTimeFormat ,
  22. CurrencyString1,
  23. CurrencyString2: string;
  24. end;
  25. var OrgFormatSettings : TOrgFormatSettings;
  26. {$endif}
  27. implementation
  28. {$linklib c}
  29. Uses
  30. SysUtils, unixtype, initc;
  31. Const
  32. {$if defined(BSD) or defined(SUNOS) or defined(aix)}
  33. // Darwin, FreeBSD, Solaris, AIX. Note the lead underscores are added.
  34. {$i clocale.inc}
  35. {$else}
  36. // checked for Linux only, but might be general glibc.
  37. __LC_CTYPE = 0;
  38. __LC_NUMERIC = 1;
  39. __LC_TIME = 2;
  40. __LC_COLLATE = 3;
  41. __LC_MONETARY = 4;
  42. __LC_MESSAGES = 5;
  43. __LC_ALL = 6;
  44. ABDAY_1 = (__LC_TIME shl 16);
  45. DAY_1 = (ABDAY_1)+7;
  46. ABMON_1 = (ABDAY_1)+14;
  47. MON_1 = (ABDAY_1)+26;
  48. AM_STR = (ABDAY_1)+38;
  49. PM_STR = (ABDAY_1)+39;
  50. D_T_FMT = (ABDAY_1)+40;
  51. D_FMT = (ABDAY_1)+41;
  52. T_FMT = (ABDAY_1)+42;
  53. T_FMT_AMPM = (ABDAY_1)+43;
  54. __DECIMAL_POINT = (__LC_NUMERIC shl 16);
  55. RADIXCHAR = __DECIMAL_POINT;
  56. __THOUSANDS_SEP = (__DECIMAL_POINT)+1;
  57. __INT_CURR_SYMBOL = (__LC_MONETARY shl 16);
  58. __CURRENCY_SYMBOL = (__INT_CURR_SYMBOL)+1;
  59. __MON_DECIMAL_POINT = (__INT_CURR_SYMBOL)+2;
  60. __MON_THOUSANDS_SEP = (__INT_CURR_SYMBOL)+3;
  61. __MON_GROUPING = (__INT_CURR_SYMBOL)+4;
  62. __POSITIVE_SIGN = (__INT_CURR_SYMBOL)+5;
  63. __NEGATIVE_SIGN = (__INT_CURR_SYMBOL)+6;
  64. __INT_FRAC_DIGITS = (__INT_CURR_SYMBOL)+7;
  65. __FRAC_DIGITS = (__INT_CURR_SYMBOL)+8;
  66. __P_CS_PRECEDES = (__INT_CURR_SYMBOL)+9;
  67. __P_SEP_BY_SPACE = (__INT_CURR_SYMBOL)+10;
  68. __N_CS_PRECEDES = (__INT_CURR_SYMBOL)+11;
  69. __N_SEP_BY_SPACE = (__INT_CURR_SYMBOL)+12;
  70. __P_SIGN_POSN = (__INT_CURR_SYMBOL)+13;
  71. __N_SIGN_POSN = (__INT_CURR_SYMBOL)+14;
  72. _NL_MONETARY_CRNCYSTR = (__INT_CURR_SYMBOL)+15;
  73. {$endif}
  74. {$ifdef netbsd}
  75. { NetBSD has a new setlocale function defined in /usr/include/locale.h
  76. that should be used }
  77. function setlocale(category: cint; locale: pchar): pchar; cdecl; external clib name '__setlocale_mb_len_max_32';
  78. {$else}
  79. function setlocale(category: cint; locale: pchar): pchar; cdecl; external clib name 'setlocale';
  80. {$endif}
  81. function nl_langinfo(__item: cint):Pchar;cdecl;external clib name 'nl_langinfo';
  82. procedure GetFormatSettings(out fmts: TFormatSettings);
  83. function GetLocaleStr(item: cint): string;
  84. begin
  85. GetLocaleStr := AnsiString(nl_langinfo(item));
  86. end;
  87. function GetLocaleChar(item: cint): char;
  88. begin
  89. GetLocaleChar := nl_langinfo(item)^;
  90. end;
  91. function SkipModifiers(const s: string; var i: integer): string;
  92. var
  93. l: Integer;
  94. begin
  95. Result := '';
  96. l := Length(s);
  97. //possible flag, with specifier or modifier - glibc exension
  98. while (i<=l) and (s[i] in ['0'..'9', '_', '-', '^', '#', 'E', 'O']) do begin
  99. Result := Result + s[i];
  100. inc(i);
  101. end;
  102. end;
  103. function IsModifier(const Mods: string; m: char): boolean;
  104. var
  105. i: integer;
  106. begin
  107. Result := False;
  108. for i := 1 to Length(Mods) do begin
  109. if Mods[i] = m then begin
  110. Result := True;
  111. Break;
  112. end;
  113. end;
  114. end;
  115. function FindSeparator(const s: string; Def: char): char;
  116. var
  117. i: integer;
  118. begin
  119. FindSeparator := Def;
  120. i := Pos('%', s);
  121. if i=0 then
  122. Exit;
  123. inc(i);
  124. SkipModifiers(s, i);
  125. inc(i);
  126. if i<=Length(s) then
  127. FindSeparator := s[i];
  128. end;
  129. function TransformFormatStr(const s: string): string;
  130. var
  131. i, l: integer;
  132. clock12:boolean;
  133. LastMod: string;
  134. begin
  135. clock12:=false; // should ampm get appended?
  136. TransformFormatStr := '';
  137. i := 1;
  138. l := Length(s);
  139. while i<=l do begin
  140. if s[i]='%' then begin
  141. inc(i);
  142. LastMod := SkipModifiers(s, i);
  143. if i>l then
  144. Exit;
  145. case s[i] of
  146. 'a': TransformFormatStr := TransformFormatStr + 'ddd';
  147. 'A': TransformFormatStr := TransformFormatStr + 'dddd';
  148. 'b': TransformFormatStr := TransformFormatStr + 'mmm';
  149. 'B': TransformFormatStr := TransformFormatStr + 'mmmm';
  150. 'c': TransformFormatStr := TransformFormatStr + 'c';
  151. //'C':
  152. 'd': if IsModifier(LastMod, '-') then
  153. TransformFormatStr := TransformFormatStr + 'd'
  154. else
  155. TransformFormatStr := TransformFormatStr + 'dd';
  156. 'D': TransformFormatStr := TransformFormatStr + 'mm"/"dd"/"yy';
  157. 'e': TransformFormatStr := TransformFormatStr + 'd';
  158. 'F': TransformFormatStr := TransformFormatStr + 'yyyy-mm-dd';
  159. 'g': TransformFormatStr := TransformFormatStr + 'yy';
  160. 'G': TransformFormatStr := TransformFormatStr + 'yyyy';
  161. 'h': TransformFormatStr := TransformFormatStr + 'mmm';
  162. 'H': TransformFormatStr := TransformFormatStr + 'hh';
  163. 'I': begin
  164. TransformFormatStr := TransformFormatStr + 'hh';
  165. clock12:=true;
  166. end;
  167. //'j':
  168. 'k': TransformFormatStr := TransformFormatStr + 'h';
  169. 'l': begin
  170. TransformFormatStr := TransformFormatStr + 'h';
  171. clock12:=true;
  172. end;
  173. 'm': if IsModifier(LastMod, '-') then
  174. TransformFormatStr := TransformFormatStr + 'm'
  175. else
  176. TransformFormatStr := TransformFormatStr + 'mm';
  177. 'M': TransformFormatStr := TransformFormatStr + 'nn';
  178. 'n': TransformFormatStr := TransformFormatStr + sLineBreak;
  179. 'p','P':
  180. begin
  181. TransformFormatStr := TransformFormatStr + 'ampm';
  182. clock12:=false;
  183. end;
  184. 'r': begin
  185. TransformFormatStr := TransformFormatStr + 'hh:nn:ss';
  186. clock12:=true;
  187. end;
  188. 'R': TransformFormatStr := TransformFormatStr + 'hh:nn';
  189. //'s':
  190. 'S': TransformFormatStr := TransformFormatStr + 'ss';
  191. 't': TransformFormatStr := TransformFormatStr + #9;
  192. 'T': TransformFormatStr := TransformFormatStr + 'hh:nn:ss';
  193. //'u':
  194. //'U':
  195. //'V':
  196. //'w':
  197. //'W':
  198. 'x': TransformFormatStr := TransformFormatStr + 'ddddd';
  199. 'X': TransformFormatStr := TransformFormatStr + 't';
  200. 'y': TransformFormatStr := TransformFormatStr + 'yy';
  201. 'Y': TransformFormatStr := TransformFormatStr + 'yyyy';
  202. //'z':
  203. //'Z':
  204. '%': TransformFormatStr := TransformFormatStr + '%';
  205. end;
  206. end else
  207. TransformFormatStr := TransformFormatStr + s[i];
  208. inc(i);
  209. end;
  210. i:=length(TransformFormatStr);
  211. if clock12 and (i>0) then
  212. begin
  213. if transformformatstr[i]<>' ' then
  214. TransformFormatStr := TransformFormatStr + ' ';
  215. TransformFormatStr := TransformFormatStr + 'ampm';
  216. end;
  217. end;
  218. const
  219. // sign prec sep
  220. NegFormatsTable: array [0..4, 0..1, 0..1] of byte = (
  221. ( (4, 15), (0, 14) ), //Parentheses surround the quantity and currency_symbol
  222. ( (5, 8), (1, 9) ), //The sign string precedes the quantity and currency_symbol
  223. ( (7, 10), (3, 11) ), //The sign string follows the quantity and currency_symbol
  224. ( (6, 13), (1, 9) ), //The sign string immediately precedes the currency_symbol
  225. ( (7, 10), (2, 12) ) //The sign string immediately follows the currency_symbol
  226. );
  227. var
  228. i: integer;
  229. prec, sep, signp: byte;
  230. {$if defined(BSD) or defined(SUNOS) or defined(aix)}
  231. plocale : plconv;
  232. {$ENDIF}
  233. begin
  234. setlocale(__LC_ALL,'');
  235. for i := 1 to 12 do
  236. begin
  237. fmts.ShortMonthNames[i]:=GetLocaleStr(ABMON_1+i-1);
  238. fmts.LongMonthNames[i]:=GetLocaleStr(MON_1+i-1);
  239. end;
  240. for i := 1 to 7 do
  241. begin
  242. fmts.ShortDayNames[i]:=GetLocaleStr(ABDAY_1+i-1);
  243. fmts.LongDayNames[i]:=GetLocaleStr(DAY_1+i-1);
  244. end;
  245. //Date stuff
  246. fmts.ShortDateFormat := GetLocaleStr(D_FMT);
  247. {$ifdef localedebug}
  248. OrgFormatSettings.ShortDateFormat:=fmts.shortdateformat;
  249. {$endif}
  250. fmts.DateSeparator := FindSeparator(fmts.ShortDateFormat, fmts.DateSeparator);
  251. fmts.ShortDateFormat := TransformFormatStr(fmts.ShortDateFormat);
  252. fmts.LongDateFormat := GetLocaleStr(D_FMT);
  253. {$ifdef localedebug}
  254. OrgFormatSettings.LongDateFormat:=fmts.longdateformat;
  255. {$endif}
  256. fmts.LongDateFormat := TransformFormatStr(fmts.LongDateFormat);
  257. //Time stuff
  258. fmts.TimeAMString := GetLocaleStr(AM_STR);
  259. fmts.TimePMString := GetLocaleStr(PM_STR);
  260. fmts.ShortTimeFormat := GetLocaleStr(T_FMT);
  261. {$ifdef localedebug}
  262. OrgFormatSettings.ShortTimeFormat:=fmts.shorttimeformat;
  263. {$endif}
  264. fmts.TimeSeparator := FindSeparator(fmts.ShortTimeFormat, fmts.TimeSeparator);
  265. fmts.ShortTimeFormat := TransformFormatStr(fmts.ShortTimeFormat);
  266. fmts.LongTimeFormat := GetLocaleStr(T_FMT_AMPM);
  267. {$ifdef localedebug}
  268. OrgFormatSettings.LongTimeFormat:=fmts.longtimeformat;
  269. {$endif}
  270. if (fmts.LongTimeFormat='') then
  271. fmts.LongTimeFormat:=fmts.ShortTimeFormat
  272. else
  273. fmts.LongTimeFormat := TransformFormatStr(fmts.LongTimeFormat);
  274. {$if defined(BSD) or defined(SUNOS) or defined(aix)}
  275. plocale:=localeconv;
  276. // for these fields there is a separate BSD derived POSIX function.
  277. if not assigned(plocale) then exit; // for now.
  278. fmts.CurrencyString:=plocale^.currency_symbol; // int_CURR_SYMBOL (in latin chars)
  279. if fmts.CurrencyString='' then
  280. fmts.CurrencyString:=plocale^.int_curr_symbol;
  281. fmts.CurrencyDecimals:=ord(plocale^.FRAC_DIGITS);
  282. {$ifdef localedebug}
  283. OrgFormatSettings.CurrencyString1:=plocale^.currency_symbol;
  284. OrgFormatSettings.CurrencyString2:=plocale^.int_curr_symbol;
  285. {$endif}
  286. prec:=ord(plocale^.P_CS_PRECEDES);
  287. sep:=ord(plocale^.P_SEP_BY_SPACE);
  288. if (prec<=1) and (sep<=1) then
  289. fmts.CurrencyFormat := byte(not boolean(prec)) + sep shl 1;
  290. prec := ord(plocale^.N_CS_PRECEDES);
  291. sep := ord(plocale^.N_SEP_BY_SPACE);
  292. signp := ord(plocale^.N_SIGN_POSN);
  293. if (signp in [0..4]) and (prec in [0, 1]) and (sep in [0, 1]) then
  294. fmts.NegCurrFormat := NegFormatsTable[signp, prec, sep];
  295. //Number stuff
  296. fmts.ThousandSeparator:=plocale^.THOUSANDS_SEP[0];
  297. {$else}
  298. //Currency stuff
  299. fmts.CurrencyString := GetLocaleStr(_NL_MONETARY_CRNCYSTR);
  300. {$ifdef localedebug}
  301. OrgFormatSettings.CurrencyString1:=fmts.currencystring;
  302. OrgFormatSettings.CurrencyString2:='';
  303. {$endif}
  304. fmts.CurrencyString := Copy(fmts.CurrencyString, 2, Length(fmts.CurrencyString));
  305. fmts.CurrencyDecimals := StrToIntDef(GetLocaleStr(__FRAC_DIGITS), fmts.CurrencyDecimals);
  306. prec := byte(GetLocaleChar(__P_CS_PRECEDES));
  307. sep := byte(GetLocaleChar(__P_SEP_BY_SPACE));
  308. if (prec<=1) and (sep<=1) then
  309. fmts.CurrencyFormat := byte(not boolean(prec)) + sep shl 1;
  310. prec := byte(GetLocaleChar(__N_CS_PRECEDES));
  311. sep := byte(GetLocaleChar(__N_SEP_BY_SPACE));
  312. signp := byte(GetLocaleChar(__N_SIGN_POSN));
  313. if (signp in [0..4]) and (prec in [0, 1]) and (sep in [0, 1]) then
  314. fmts.NegCurrFormat := NegFormatsTable[signp, prec, sep];
  315. //Number stuff
  316. fmts.ThousandSeparator:=GetLocaleChar(__THOUSANDS_SEP);
  317. Sep := ord(GetLocaleChar(__MON_THOUSANDS_SEP));
  318. if fmts.ThousandSeparator=#0 then
  319. fmts.ThousandSeparator := char(Sep);
  320. {$endif}
  321. fmts.DecimalSeparator:=GetLocaleChar(RADIXCHAR);
  322. end;
  323. initialization
  324. GetFormatSettings(DefaultFormatSettings);
  325. end.