timezone.inc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. {
  2. Support for timezone info in /usr/share/timezone
  3. }
  4. type
  5. ttzhead=packed record
  6. tzh_identifier : array[0..3] of AnsiChar;
  7. tzh_version : AnsiChar;
  8. tzh_reserved : array[0..14] of byte;
  9. tzh_ttisgmtcnt,
  10. tzh_ttisstdcnt,
  11. tzh_leapcnt,
  12. tzh_timecnt,
  13. tzh_typecnt,
  14. tzh_charcnt : longint;
  15. end;
  16. pttinfo=^tttinfo;
  17. tttinfo=packed record
  18. offset : longint;
  19. isdst : boolean;
  20. idx : byte;
  21. isstd : byte;
  22. isgmt : byte;
  23. end;
  24. pleap=^tleap;
  25. tleap=record
  26. transition : int64;
  27. change : int64;
  28. end;
  29. var
  30. num_transitions,
  31. num_leaps,
  32. num_types : longint;
  33. transitions : PInt64 = nil;
  34. type_idxs : pbyte = Nil;
  35. types : pttinfo = Nil;
  36. zone_names : pchar = Nil;
  37. leaps : pleap = Nil;
  38. function find_transition(timer:int64;timerIsUTC:Boolean;var trans_start,trans_end:int64):pttinfo;
  39. var
  40. i,L,R,CompareRes : longint;
  41. found : boolean;
  42. function DoCompare: longint;
  43. var
  44. timerUTC: int64;
  45. begin
  46. if not timerIsUTC then
  47. timerUTC:=timer-types[type_idxs[i-1]].offset
  48. else
  49. timerUTC:=timer;
  50. if timerUTC<transitions[i-1] then
  51. Exit(-1)
  52. else
  53. if timerUTC>=transitions[i] then
  54. Exit(1)
  55. else
  56. Exit(0);
  57. end;
  58. begin
  59. if (num_transitions=0) or (timer<transitions[0]) then
  60. begin
  61. i:=0;
  62. while (i<num_types) and (types[i].isdst) do
  63. inc(i);
  64. if (i=num_types) then
  65. i:=0;
  66. { unknown transition boundaries }
  67. trans_start:=low(trans_start);
  68. trans_end:=high(trans_end);
  69. end
  70. else
  71. begin
  72. // Use binary search.
  73. L := 1;
  74. R := num_transitions-1;
  75. found := false;
  76. while not found and (L<=R) do
  77. begin
  78. I := L + (R - L) div 2;
  79. CompareRes := DoCompare;
  80. if (CompareRes>0) then
  81. L := I+1
  82. else begin
  83. R := I-1;
  84. if (CompareRes=0) then
  85. found:=true; // break cycle
  86. end;
  87. end;
  88. if not found then
  89. Exit(nil);
  90. trans_start:=transitions[i-1];
  91. trans_end:=transitions[i];
  92. i:=type_idxs[i-1];
  93. end;
  94. find_transition:=@types[i];
  95. end;
  96. procedure DoGetLocalTimezone(info:pttinfo;const trans_start,trans_end:int64;var ATZInfo:TTZInfo);
  97. begin
  98. ATZInfo.validsince:=trans_start;
  99. ATZInfo.validuntil:=trans_end;
  100. ATZInfo.Daylight:=info^.isdst;
  101. ATZInfo.Seconds:=info^.offset;
  102. end;
  103. procedure DoGetLocalTimezoneEx(timer:int64;info:pttinfo;var ATZInfoEx:TTZInfoEx);
  104. var
  105. i : longint;
  106. names: array[Boolean] of pchar;
  107. begin
  108. names[true]:=nil;
  109. names[false]:=nil;
  110. ATZInfoEx.leap_hit:=0;
  111. ATZInfoEx.leap_correct:=0;
  112. i:=0;
  113. while (i<num_types) do
  114. begin
  115. names[types[i].isdst]:=@zone_names[types[i].idx];
  116. inc(i);
  117. end;
  118. names[info^.isdst]:=@zone_names[info^.idx];
  119. ATZInfoEx.name[true]:=names[true];
  120. ATZInfoEx.name[false]:=names[false];
  121. i:=num_leaps;
  122. repeat
  123. if i=0 then
  124. exit;
  125. dec(i);
  126. until (timer>leaps[i].transition);
  127. ATZInfoEx.leap_correct:=leaps[i].change;
  128. if (timer=leaps[i].transition) and
  129. (((i=0) and (leaps[i].change>0)) or
  130. (leaps[i].change>leaps[i-1].change)) then
  131. begin
  132. ATZInfoEx.leap_hit:=1;
  133. while (i>0) and
  134. (leaps[i].transition=leaps[i-1].transition+1) and
  135. (leaps[i].change=leaps[i-1].change+1) do
  136. begin
  137. inc(ATZInfoEx.leap_hit);
  138. dec(i);
  139. end;
  140. end;
  141. end;
  142. function GetLocalTimezone(timer:int64;timerIsUTC:Boolean;var ATZInfo:TTZInfo):Boolean;
  143. var
  144. info: pttinfo;
  145. trans_start,trans_end,timerUTC: int64;
  146. begin
  147. { check if time is in current global Tzinfo }
  148. ATZInfo:=CurrentTZinfo[InterlockedExchangeAdd(CurrentTZindex, 0)];
  149. if not timerIsUTC then
  150. timerUTC:=timer-ATZInfo.seconds
  151. else
  152. timerUTC:=timer;
  153. if (ATZInfo.validsince<=timerUTC) and (timerUTC<ATZInfo.validuntil) then
  154. Exit(True);
  155. LockTZInfo;
  156. info:=find_transition(timer,timerIsUTC,trans_start,trans_end);
  157. GetLocalTimezone:=assigned(info);
  158. if GetLocalTimezone then
  159. DoGetLocalTimezone(info,trans_start,trans_end,ATZInfo);
  160. UnlockTZInfo;
  161. end;
  162. function GetLocalTimezone(timer:int64;timerIsUTC:Boolean;var ATZInfo:TTZInfo;var ATZInfoEx:TTZInfoEx):Boolean;
  163. var
  164. info: pttinfo;
  165. trans_start,trans_end,timerUTC: int64;
  166. begin
  167. { check if time is in current global Tzinfo }
  168. ATZInfo:=CurrentTZinfo[InterlockedExchangeAdd(CurrentTZindex, 0)];
  169. if not timerIsUTC then
  170. timerUTC:=timer-ATZInfo.seconds
  171. else
  172. timerUTC:=timer;
  173. if (ATZInfo.validsince<=timerUTC) and (timerUTC<ATZInfo.validuntil) then
  174. begin
  175. ATZInfoEx:=TZInfoEx;
  176. Exit(True);
  177. end;
  178. { not current - search through all }
  179. LockTZInfo;
  180. info:=find_transition(timer,timerIsUTC,trans_start,trans_end);
  181. GetLocalTimezone:=assigned(info);
  182. if GetLocalTimezone then
  183. begin
  184. DoGetLocalTimezone(info,trans_start,trans_end,ATZInfo);
  185. DoGetLocalTimezoneEx(timer,info,ATZInfoEx);
  186. end;
  187. UnlockTZInfo;
  188. end;
  189. procedure RefreshTZInfo;
  190. var
  191. NewTZInfo: TTZInfo;
  192. NewTZInfoEx: TTZInfoEx;
  193. begin
  194. LockTZInfo;
  195. if GetLocalTimezone(fptime,false,NewTZInfo,NewTZInfoEx) then
  196. SetTZInfo(NewTZInfo,NewTZInfoEx);
  197. UnlockTZInfo;
  198. end;
  199. Const
  200. DefaultTimeZoneDir = '/usr/share/zoneinfo';
  201. function TimeZoneDir : ShortString;
  202. begin
  203. // Observe TZDIR environment variable.
  204. TimeZoneDir:=fpgetenv('TZDIR');
  205. if TimeZoneDir='' then
  206. TimeZoneDir:=DefaultTimeZoneDir;
  207. if TimeZoneDir[length(TimeZoneDir)]<>'/' then
  208. TimeZoneDir:=TimeZoneDir+'/';
  209. end;
  210. function ReadTimezoneFile(fn:string) : Boolean;
  211. function decode(const l:longint):longint;
  212. begin
  213. {$IFDEF ENDIAN_LITTLE}
  214. decode:=SwapEndian(l);
  215. {$ELSE}
  216. decode:=l;
  217. {$ENDIF}
  218. end;
  219. function decode(const l:int64):int64;
  220. begin
  221. {$IFDEF ENDIAN_LITTLE}
  222. decode:=SwapEndian(l);
  223. {$ELSE}
  224. decode:=l;
  225. {$ENDIF}
  226. end;
  227. const
  228. bufsize = 2048;
  229. var
  230. buf : array[0..bufsize-1] of byte;
  231. bufptr : pbyte;
  232. f : longint;
  233. tzhead : ttzhead;
  234. procedure readfilebuf;
  235. begin
  236. bufptr := @buf[0];
  237. fpread(f, buf, bufsize);
  238. end;
  239. function readbufbyte: byte;
  240. begin
  241. if bufptr > @buf[bufsize-1] then
  242. readfilebuf;
  243. readbufbyte := bufptr^;
  244. inc(bufptr);
  245. end;
  246. function readbuf(dest:pointer; count: integer): integer;
  247. var
  248. numbytes: integer;
  249. begin
  250. readbuf := 0;
  251. repeat
  252. numbytes := (@buf[bufsize-1] + 1) - bufptr;
  253. if numbytes > count then
  254. numbytes := count;
  255. if numbytes > 0 then
  256. begin
  257. if assigned(dest) then
  258. move(bufptr^, dest^, numbytes);
  259. inc(bufptr, numbytes);
  260. dec(count, numbytes);
  261. inc(readbuf, numbytes);
  262. inc(dest, numbytes);
  263. end;
  264. if count > 0 then
  265. readfilebuf
  266. else
  267. break;
  268. until false;
  269. end;
  270. function readheader: boolean;
  271. var
  272. i : longint;
  273. begin
  274. i:=readbuf(@tzhead,sizeof(tzhead));
  275. if i<>sizeof(tzhead) then
  276. exit(False);
  277. tzhead.tzh_timecnt:=decode(tzhead.tzh_timecnt);
  278. tzhead.tzh_typecnt:=decode(tzhead.tzh_typecnt);
  279. tzhead.tzh_charcnt:=decode(tzhead.tzh_charcnt);
  280. tzhead.tzh_leapcnt:=decode(tzhead.tzh_leapcnt);
  281. tzhead.tzh_ttisstdcnt:=decode(tzhead.tzh_ttisstdcnt);
  282. tzhead.tzh_ttisgmtcnt:=decode(tzhead.tzh_ttisgmtcnt);
  283. readheader:=(tzhead.tzh_identifier[0]='T') and (tzhead.tzh_identifier[1]='Z')
  284. and (tzhead.tzh_identifier[2]='i') and (tzhead.tzh_identifier[3]='f');
  285. end;
  286. procedure AllocFields;
  287. begin
  288. num_transitions:=tzhead.tzh_timecnt;
  289. num_types:=tzhead.tzh_typecnt;
  290. num_leaps:=tzhead.tzh_leapcnt;
  291. reallocmem(transitions,num_transitions*sizeof(int64));
  292. reallocmem(type_idxs,num_transitions);
  293. reallocmem(types,num_types*sizeof(tttinfo));
  294. reallocmem(zone_names,tzhead.tzh_charcnt);
  295. reallocmem(leaps,num_leaps*sizeof(tleap));
  296. end;
  297. function readdata: boolean;
  298. var
  299. i : longint;
  300. longval: longint;
  301. version: longint;
  302. begin
  303. if tzhead.tzh_version='2' then
  304. begin
  305. version:=2;
  306. // skip version 0
  307. readbuf(nil,
  308. tzhead.tzh_timecnt*4 // transitions
  309. +tzhead.tzh_timecnt // type_idxs
  310. +tzhead.tzh_typecnt*6 // types
  311. +tzhead.tzh_charcnt // zone_names
  312. +tzhead.tzh_leapcnt*8 // leaps
  313. +tzhead.tzh_ttisstdcnt // isstd
  314. +tzhead.tzh_ttisgmtcnt // isgmt
  315. );
  316. readheader; // read version 2 header
  317. if tzhead.tzh_version<>'2' then
  318. Exit(False);
  319. end
  320. else
  321. version:=0;
  322. AllocFields;
  323. if version=2 then
  324. begin // read 64bit values
  325. readbuf(transitions,num_transitions*sizeof(int64));
  326. for i:=0 to num_transitions-1 do
  327. transitions[i]:=decode(transitions[i]);
  328. end
  329. else
  330. begin // read 32bit values
  331. for i:=0 to num_transitions-1 do
  332. begin
  333. readbuf(@longval,sizeof(longval));
  334. transitions[i]:=decode(longval);
  335. end;
  336. end;
  337. readbuf(type_idxs,num_transitions);
  338. for i:=0 to num_types-1 do
  339. begin
  340. readbuf(@types[i].offset,sizeof(LongInt));
  341. types[i].offset:=decode(types[i].offset);
  342. readbuf(@types[i].isdst,1);
  343. readbuf(@types[i].idx,1);
  344. types[i].isstd:=0;
  345. types[i].isgmt:=0;
  346. end;
  347. readbuf(zone_names,tzhead.tzh_charcnt);
  348. if version=2 then
  349. begin // read 64bit values
  350. for i:=0 to num_leaps-1 do
  351. begin
  352. readbuf(@leaps[i].transition,sizeof(int64));
  353. readbuf(@leaps[i].change,sizeof(int64));
  354. leaps[i].transition:=decode(leaps[i].transition);
  355. leaps[i].change:=decode(leaps[i].change);
  356. end;
  357. end
  358. else
  359. begin
  360. for i:=0 to num_leaps-1 do
  361. begin
  362. readbuf(@longval,sizeof(longval));
  363. leaps[i].transition:=decode(longval);
  364. readbuf(@longval,sizeof(longval));
  365. leaps[i].change:=decode(longval);
  366. end;
  367. end;
  368. for i:=0 to tzhead.tzh_ttisstdcnt-1 do
  369. types[i].isstd:=byte(readbufbyte<>0);
  370. for i:=0 to tzhead.tzh_ttisgmtcnt-1 do
  371. types[i].isgmt:=byte(readbufbyte<>0);
  372. readdata:=true;
  373. end;
  374. begin
  375. if fn='' then
  376. fn:='localtime';
  377. if fn[1]<>'/' then
  378. fn:='/usr/share/zoneinfo/'+fn;
  379. f:=fpopen(fn,Open_RdOnly);
  380. if f<0 then
  381. exit(False);
  382. bufptr := @buf[bufsize-1]+1;
  383. tzhead:=default(ttzhead);
  384. LockTZInfo;
  385. ReadTimezoneFile:=(readheader() and readdata());
  386. UnlockTZInfo;
  387. fpclose(f);
  388. end;
  389. Const
  390. // Debian system; contains location of timezone file.
  391. TimeZoneLocationFile = '/etc/timezone';
  392. // SuSE has link in /usr/lib/zoneinfo/localtime to /etc/localtime
  393. // RedHat uses /etc/localtime
  394. TimeZoneFile = '/etc/localtime'; // POSIX
  395. AltTimeZoneFile = '/usr/lib/zoneinfo/localtime'; // Other
  396. iOSTimeZoneFile = '/var/db/timezone/localtime'; // iOS
  397. {$ifdef BSD}
  398. BSDTimeZonefile = DefaultTimeZoneDir; // BSD usually is POSIX
  399. // compliant though
  400. {$ENDIF}
  401. {$ifndef FPC_HAS_GETTIMEZONEFILE}
  402. function GetTimezoneFile:shortstring;
  403. var
  404. f,len : longint;
  405. fn,s : shortstring;
  406. info : stat;
  407. begin
  408. GetTimezoneFile:='';
  409. // Observe TZ variable.
  410. fn:=fpgetenv('TZ');
  411. if (fn<>'') then
  412. if (fn[1]=':') then
  413. begin
  414. Delete(fn,1,1);
  415. if (fn<>'') then
  416. begin
  417. if (fn[1]<>'/') then
  418. Exit(TimeZoneDir+fn);
  419. Exit(fn);
  420. end;
  421. end;
  422. if (fn='') then
  423. fn:=TimeZoneLocationFile;
  424. f:=fpopen(TimeZoneLocationFile,Open_RdOnly);
  425. if f>0 then
  426. begin
  427. len:=fpread(f,s[1],high(s));
  428. s[0]:=chr(len);
  429. len:=pos(#10,s);
  430. if len<>0 then
  431. s[0]:=chr(len-1);
  432. fpclose(f);
  433. GetTimezoneFile:=s;
  434. end
  435. // Try SuSE
  436. else if fpstat(TimeZoneFile,{$ifdef oldlinuxstat}baseunix.stat(info){$else}info{$endif})>=0 then
  437. GetTimeZoneFile:=TimeZoneFile
  438. // Try RedHat
  439. else If fpstat(AltTimeZoneFile,{$ifdef oldlinuxstat}baseunix.stat(info){$else}info{$endif})>=0 then
  440. GetTimeZoneFile:=AltTimeZoneFile
  441. {$ifdef BSD}
  442. // else
  443. // If fpstat(BSDTimeZoneFile,{$ifdef oldlinuxstat}baseunix.stat(info){$else}info{$endif})>=0 then
  444. // GetTimeZoneFile:=BSDTimeZoneFile
  445. {$ENDIF}
  446. {$if (defined(darwin) and defined(arm)) or defined(iphonesim)}
  447. else If fpstat(iOSTimeZoneFile,info)>=0 then
  448. GetTimeZoneFile:=iOSTimeZoneFile
  449. {$endif}
  450. end;
  451. {$endif ndef FPC_HAS_GETTIMEZONEFILE}
  452. procedure InitLocalTime;
  453. begin
  454. ReadTimezoneFile(GetTimezoneFile);
  455. RefreshTZInfo;
  456. end;
  457. procedure DoneLocalTime;
  458. begin
  459. if assigned(transitions) then
  460. freemem(transitions);
  461. transitions:=nil;
  462. if assigned(type_idxs) then
  463. freemem(type_idxs);
  464. type_idxs:=nil;
  465. if assigned(types) then
  466. freemem(types);
  467. types:=nil;
  468. if assigned(zone_names) then
  469. freemem(zone_names);
  470. zone_names:=Nil;
  471. if assigned(leaps) then
  472. freemem(leaps);
  473. leaps:=nil;
  474. num_transitions:=0;
  475. num_leaps:=0;
  476. num_types:=0;
  477. end;
  478. Procedure ReReadLocalTime;
  479. begin
  480. LockTZInfo;
  481. DoneLocalTime;
  482. InitLocalTime;
  483. UnlockTZInfo;
  484. end;