timezone.inc 14 KB

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