clocale.pp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  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)}
  33. // Darwin and FreeBSD. 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. function setlocale(category: cint; locale: pchar): pchar; cdecl; external clib name 'setlocale';
  75. function nl_langinfo(__item: cint):Pchar;cdecl;external clib name 'nl_langinfo';
  76. procedure GetFormatSettings(out fmts: TFormatSettings);
  77. function GetLocaleStr(item: cint): string;
  78. begin
  79. GetLocaleStr := AnsiString(nl_langinfo(item));
  80. end;
  81. function GetLocaleChar(item: cint): char;
  82. begin
  83. GetLocaleChar := nl_langinfo(item)^;
  84. end;
  85. procedure OmitModifiers(const s: string; var i: integer);
  86. var
  87. l: Integer;
  88. begin
  89. l := Length(s);
  90. //possible flag, with specifier or modifier - glibc exension
  91. while (i<=l) and (s[i] in ['0'..'9', '_', '-', '^', '#', 'E', 'O']) do
  92. inc(i);
  93. end;
  94. function FindSeparator(const s: string; Def: char): char;
  95. var
  96. i: integer;
  97. begin
  98. FindSeparator := Def;
  99. i := Pos('%', s);
  100. if i=0 then
  101. Exit;
  102. inc(i);
  103. OmitModifiers(s, i);
  104. inc(i);
  105. if i<=Length(s) then
  106. FindSeparator := s[i];
  107. end;
  108. function TransformFormatStr(const s: string): string;
  109. var
  110. i, l: integer;
  111. clock12:boolean;
  112. begin
  113. clock12:=false; // should ampm get appended?
  114. TransformFormatStr := '';
  115. i := 1;
  116. l := Length(s);
  117. while i<=l do begin
  118. if s[i]='%' then begin
  119. inc(i);
  120. OmitModifiers(s, i);
  121. if i>l then
  122. Exit;
  123. case s[i] of
  124. 'a': TransformFormatStr := TransformFormatStr + 'ddd';
  125. 'A': TransformFormatStr := TransformFormatStr + 'dddd';
  126. 'b': TransformFormatStr := TransformFormatStr + 'mmm';
  127. 'B': TransformFormatStr := TransformFormatStr + 'mmmm';
  128. 'c': TransformFormatStr := TransformFormatStr + 'c';
  129. //'C':
  130. 'd': TransformFormatStr := TransformFormatStr + 'dd';
  131. 'D': TransformFormatStr := TransformFormatStr + 'mm"/"dd"/"yy';
  132. 'e': TransformFormatStr := TransformFormatStr + 'd';
  133. 'F': TransformFormatStr := TransformFormatStr + 'yyyy-mm-dd';
  134. 'g': TransformFormatStr := TransformFormatStr + 'yy';
  135. 'G': TransformFormatStr := TransformFormatStr + 'yyyy';
  136. 'h': TransformFormatStr := TransformFormatStr + 'mmm';
  137. 'H': TransformFormatStr := TransformFormatStr + 'hh';
  138. 'I': begin
  139. TransformFormatStr := TransformFormatStr + 'hh';
  140. clock12:=true;
  141. end;
  142. //'j':
  143. 'k': TransformFormatStr := TransformFormatStr + 'h';
  144. 'l': begin
  145. TransformFormatStr := TransformFormatStr + 'h';
  146. clock12:=true;
  147. end;
  148. 'm': TransformFormatStr := TransformFormatStr + 'mm';
  149. 'M': TransformFormatStr := TransformFormatStr + 'nn';
  150. 'n': TransformFormatStr := TransformFormatStr + sLineBreak;
  151. 'p','P':
  152. begin
  153. TransformFormatStr := TransformFormatStr + 'ampm';
  154. clock12:=false;
  155. end;
  156. 'r': begin
  157. TransformFormatStr := TransformFormatStr + 'hh:nn:ss';
  158. clock12:=true;
  159. end;
  160. 'R': TransformFormatStr := TransformFormatStr + 'hh:nn';
  161. //'s':
  162. 'S': TransformFormatStr := TransformFormatStr + 'ss';
  163. 't': TransformFormatStr := TransformFormatStr + #9;
  164. 'T': TransformFormatStr := TransformFormatStr + 'hh:nn:ss';
  165. //'u':
  166. //'U':
  167. //'V':
  168. //'w':
  169. //'W':
  170. 'x': TransformFormatStr := TransformFormatStr + 'ddddd';
  171. 'X': TransformFormatStr := TransformFormatStr + 't';
  172. 'y': TransformFormatStr := TransformFormatStr + 'yy';
  173. 'Y': TransformFormatStr := TransformFormatStr + 'yyyy';
  174. //'z':
  175. //'Z':
  176. '%': TransformFormatStr := TransformFormatStr + '%';
  177. end;
  178. end else
  179. TransformFormatStr := TransformFormatStr + s[i];
  180. inc(i);
  181. end;
  182. i:=length(TransformFormatStr);
  183. if clock12 and (i>0) then
  184. begin
  185. if transformformatstr[i]<>' ' then
  186. TransformFormatStr := TransformFormatStr + ' ';
  187. TransformFormatStr := TransformFormatStr + 'ampm';
  188. end;
  189. end;
  190. const
  191. // sign prec sep
  192. NegFormatsTable: array [0..4, 0..1, 0..1] of byte = (
  193. ( (4, 15), (0, 14) ), //Parentheses surround the quantity and currency_symbol
  194. ( (5, 8), (1, 9) ), //The sign string precedes the quantity and currency_symbol
  195. ( (7, 10), (3, 11) ), //The sign string follows the quantity and currency_symbol
  196. ( (6, 13), (1, 9) ), //The sign string immediately precedes the currency_symbol
  197. ( (7, 10), (2, 12) ) //The sign string immediately follows the currency_symbol
  198. );
  199. var
  200. i: integer;
  201. prec, sep, signp: byte;
  202. {$if defined(BSD) or defined(SUNOS)}
  203. plocale : plconv;
  204. {$ENDIF}
  205. begin
  206. setlocale(__LC_ALL,'');
  207. for i := 1 to 12 do
  208. begin
  209. fmts.ShortMonthNames[i]:=GetLocaleStr(ABMON_1+i-1);
  210. fmts.LongMonthNames[i]:=GetLocaleStr(MON_1+i-1);
  211. end;
  212. for i := 1 to 7 do
  213. begin
  214. fmts.ShortDayNames[i]:=GetLocaleStr(ABDAY_1+i-1);
  215. fmts.LongDayNames[i]:=GetLocaleStr(DAY_1+i-1);
  216. end;
  217. //Date stuff
  218. fmts.ShortDateFormat := GetLocaleStr(D_FMT);
  219. {$ifdef localedebug}
  220. OrgFormatSettings.ShortDateFormat:=fmts.shortdateformat;
  221. {$endif}
  222. fmts.DateSeparator := FindSeparator(fmts.ShortDateFormat, fmts.DateSeparator);
  223. fmts.ShortDateFormat := TransformFormatStr(fmts.ShortDateFormat);
  224. fmts.LongDateFormat := GetLocaleStr(D_T_FMT);
  225. {$ifdef localedebug}
  226. OrgFormatSettings.LongDateFormat:=fmts.longdateformat;
  227. {$endif}
  228. fmts.LongDateFormat := TransformFormatStr(fmts.LongDateFormat);
  229. //Time stuff
  230. fmts.TimeAMString := GetLocaleStr(AM_STR);
  231. fmts.TimePMString := GetLocaleStr(PM_STR);
  232. fmts.ShortTimeFormat := GetLocaleStr(T_FMT);
  233. {$ifdef localedebug}
  234. OrgFormatSettings.ShortTimeFormat:=fmts.shorttimeformat;
  235. {$endif}
  236. fmts.TimeSeparator := FindSeparator(fmts.ShortTimeFormat, fmts.TimeSeparator);
  237. fmts.ShortTimeFormat := TransformFormatStr(fmts.ShortTimeFormat);
  238. fmts.LongTimeFormat := GetLocaleStr(T_FMT_AMPM);
  239. {$ifdef localedebug}
  240. OrgFormatSettings.LongTimeFormat:=fmts.longtimeformat;
  241. {$endif}
  242. if (fmts.LongTimeFormat='') then
  243. fmts.LongTimeFormat:=fmts.ShortTimeFormat
  244. else
  245. fmts.LongTimeFormat := TransformFormatStr(fmts.LongTimeFormat);
  246. {$if defined(BSD) or defined(SUNOS)}
  247. plocale:=localeconv;
  248. // for these fields there is a separate BSD derived POSIX function.
  249. if not assigned(plocale) then exit; // for now.
  250. fmts.CurrencyString:=plocale^.currency_symbol; // int_CURR_SYMBOL (in latin chars)
  251. if fmts.CurrencyString='' then
  252. fmts.CurrencyString:=plocale^.int_curr_symbol;
  253. fmts.CurrencyDecimals:=ord(plocale^.FRAC_DIGITS);
  254. {$ifdef localedebug}
  255. OrgFormatSettings.CurrencyString1:=plocale^.currency_symbol;
  256. OrgFormatSettings.CurrencyString2:=plocale^.int_curr_symbol;
  257. {$endif}
  258. prec:=ord(plocale^.P_CS_PRECEDES);
  259. sep:=ord(plocale^.P_SEP_BY_SPACE);
  260. if (prec<=1) and (sep<=1) then
  261. fmts.CurrencyFormat := byte(not boolean(prec)) + sep shl 1;
  262. prec := ord(plocale^.N_CS_PRECEDES);
  263. sep := ord(plocale^.N_SEP_BY_SPACE);
  264. signp := ord(plocale^.N_SIGN_POSN);
  265. if (signp in [0..4]) and (prec in [0, 1]) and (sep in [0, 1]) then
  266. fmts.NegCurrFormat := NegFormatsTable[signp, prec, sep];
  267. //Number stuff
  268. fmts.ThousandSeparator:=plocale^.THOUSANDS_SEP[0];
  269. {$else}
  270. //Currency stuff
  271. fmts.CurrencyString := GetLocaleStr(_NL_MONETARY_CRNCYSTR);
  272. {$ifdef localedebug}
  273. OrgFormatSettings.CurrencyString1:=fmts.currencystring;
  274. OrgFormatSettings.CurrencyString2:='';
  275. {$endif}
  276. fmts.CurrencyString := Copy(fmts.CurrencyString, 2, Length(fmts.CurrencyString));
  277. fmts.CurrencyDecimals := StrToIntDef(GetLocaleStr(__FRAC_DIGITS), fmts.CurrencyDecimals);
  278. prec := byte(GetLocaleChar(__P_CS_PRECEDES));
  279. sep := byte(GetLocaleChar(__P_SEP_BY_SPACE));
  280. if (prec<=1) and (sep<=1) then
  281. fmts.CurrencyFormat := byte(not boolean(prec)) + sep shl 1;
  282. prec := byte(GetLocaleChar(__N_CS_PRECEDES));
  283. sep := byte(GetLocaleChar(__N_SEP_BY_SPACE));
  284. signp := byte(GetLocaleChar(__N_SIGN_POSN));
  285. if (signp in [0..4]) and (prec in [0, 1]) and (sep in [0, 1]) then
  286. fmts.NegCurrFormat := NegFormatsTable[signp, prec, sep];
  287. //Number stuff
  288. fmts.ThousandSeparator:=GetLocaleChar(__THOUSANDS_SEP);
  289. Sep := ord(GetLocaleChar(__MON_THOUSANDS_SEP));
  290. if fmts.ThousandSeparator=#0 then
  291. fmts.ThousandSeparator := char(Sep);
  292. {$endif}
  293. fmts.DecimalSeparator:=GetLocaleChar(RADIXCHAR);
  294. end;
  295. initialization
  296. GetFormatSettings(DefaultFormatSettings);
  297. end.