mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-04-07 02:48:07 +02:00
581 lines
14 KiB
PHP
581 lines
14 KiB
PHP
{
|
|
|
|
Support for timezone info in /usr/share/timezone
|
|
}
|
|
|
|
type
|
|
|
|
ttzhead=packed record
|
|
tzh_identifier : array[0..3] of AnsiChar;
|
|
tzh_version : AnsiChar;
|
|
tzh_reserved : array[0..14] of byte;
|
|
tzh_ttisgmtcnt,
|
|
tzh_ttisstdcnt,
|
|
tzh_leapcnt,
|
|
tzh_timecnt,
|
|
tzh_typecnt,
|
|
tzh_charcnt : longint;
|
|
end;
|
|
|
|
pttinfo=^tttinfo;
|
|
tttinfo=packed record
|
|
offset : longint;
|
|
isdst : boolean;
|
|
idx : byte;
|
|
isstd : byte;
|
|
isgmt : byte;
|
|
end;
|
|
|
|
pleap=^tleap;
|
|
tleap=record
|
|
transition : int64;
|
|
change : longint;
|
|
end;
|
|
|
|
var
|
|
num_transitions,
|
|
num_leaps,
|
|
num_types : longint;
|
|
|
|
transitions : PInt64 = nil;
|
|
type_idxs : pbyte = Nil;
|
|
types : pttinfo = Nil;
|
|
zone_names : PAnsiChar = Nil;
|
|
leaps : pleap = Nil;
|
|
|
|
function find_transition(timer:int64;timerIsUTC:Boolean;var trans_start,trans_end:int64):pttinfo;
|
|
var
|
|
i,L,R,CompareRes : longint;
|
|
found : boolean;
|
|
|
|
function DoCompare: longint;
|
|
var
|
|
timerUTC: int64;
|
|
begin
|
|
if not timerIsUTC then
|
|
timerUTC:=timer-types[type_idxs[i-1]].offset
|
|
else
|
|
timerUTC:=timer;
|
|
if timerUTC<transitions[i-1] then
|
|
Exit(-1)
|
|
else
|
|
if timerUTC>=transitions[i] then
|
|
Exit(1)
|
|
else
|
|
Exit(0);
|
|
end;
|
|
var
|
|
timerLoUTC, timerHiUTC: int64;
|
|
begin
|
|
if (num_transitions>0) and not timerIsUTC then
|
|
begin
|
|
timerLoUTC:=timer-types[type_idxs[0]].offset;
|
|
timerHiUTC:=timer-types[type_idxs[num_transitions-1]].offset;
|
|
end
|
|
else
|
|
begin
|
|
timerLoUTC:=timer;
|
|
timerHiUTC:=timer;
|
|
end;
|
|
|
|
if (num_transitions=0) or (timerLoUTC<transitions[0]) then
|
|
{ timer is before the first transition }
|
|
begin
|
|
i:=0;
|
|
while (i<num_types) and (types[i].isdst) do
|
|
inc(i);
|
|
if (i=num_types) then
|
|
i:=0;
|
|
{ unknown transition boundaries }
|
|
trans_start:=low(trans_start);
|
|
trans_end:=high(trans_end);
|
|
end
|
|
else
|
|
if (num_transitions>0) and (timerHiUTC>=transitions[num_transitions-1]) then
|
|
{ timer is after the last transition }
|
|
begin
|
|
i:=type_idxs[num_transitions-1];
|
|
trans_start:=transitions[num_transitions-1];
|
|
trans_end:=high(trans_end);
|
|
end
|
|
else
|
|
{ timer inbetween }
|
|
begin
|
|
// Use binary search.
|
|
L := 1;
|
|
R := num_transitions-1;
|
|
found := false;
|
|
while not found and (L<=R) do
|
|
begin
|
|
I := L + (R - L) div 2;
|
|
CompareRes := DoCompare;
|
|
if (CompareRes>0) then
|
|
L := I+1
|
|
else begin
|
|
R := I-1;
|
|
if (CompareRes=0) then
|
|
found:=true; // break cycle
|
|
end;
|
|
end;
|
|
if not found then
|
|
Exit(nil);
|
|
trans_start:=transitions[i-1];
|
|
trans_end:=transitions[i];
|
|
i:=type_idxs[i-1];
|
|
end;
|
|
find_transition:=@types[i];
|
|
end;
|
|
|
|
|
|
procedure DoGetLocalTimezone(info:pttinfo;const trans_start,trans_end:int64;var ATZInfo:TTZInfo);
|
|
begin
|
|
ATZInfo.validsince:=trans_start;
|
|
ATZInfo.validuntil:=trans_end;
|
|
ATZInfo.Daylight:=info^.isdst;
|
|
ATZInfo.Seconds:=info^.offset;
|
|
end;
|
|
|
|
procedure DoGetLocalTimezoneEx(timer:int64;info:pttinfo;var ATZInfoEx:TTZInfoEx);
|
|
var
|
|
i : longint;
|
|
names: array[Boolean] of PAnsiChar;
|
|
begin
|
|
names[true]:=nil;
|
|
names[false]:=nil;
|
|
ATZInfoEx.leap_hit:=0;
|
|
ATZInfoEx.leap_correct:=0;
|
|
|
|
i:=0;
|
|
while (i<num_types) do
|
|
begin
|
|
names[types[i].isdst]:=@zone_names[types[i].idx];
|
|
inc(i);
|
|
end;
|
|
names[info^.isdst]:=@zone_names[info^.idx];
|
|
ATZInfoEx.name[true]:=names[true];
|
|
ATZInfoEx.name[false]:=names[false];
|
|
i:=num_leaps;
|
|
repeat
|
|
if i=0 then
|
|
exit;
|
|
dec(i);
|
|
until (timer>leaps[i].transition);
|
|
ATZInfoEx.leap_correct:=leaps[i].change;
|
|
if (timer=leaps[i].transition) and
|
|
(((i=0) and (leaps[i].change>0)) or
|
|
(leaps[i].change>leaps[i-1].change)) then
|
|
begin
|
|
ATZInfoEx.leap_hit:=1;
|
|
while (i>0) and
|
|
(leaps[i].transition=leaps[i-1].transition+1) and
|
|
(leaps[i].change=leaps[i-1].change+1) do
|
|
begin
|
|
inc(ATZInfoEx.leap_hit);
|
|
dec(i);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function GetLocalTimezone(timer:int64;timerIsUTC:Boolean;var ATZInfo:TTZInfo):Boolean;
|
|
var
|
|
info: pttinfo;
|
|
trans_start,trans_end,timerUTC: int64;
|
|
begin
|
|
{ check if time is in current global Tzinfo }
|
|
ATZInfo:=CurrentTZinfo[InterlockedExchangeAdd(CurrentTZindex, 0)];
|
|
if not timerIsUTC then
|
|
timerUTC:=timer-ATZInfo.seconds
|
|
else
|
|
timerUTC:=timer;
|
|
if (ATZInfo.validsince<=timerUTC) and (timerUTC<ATZInfo.validuntil) then
|
|
Exit(True);
|
|
|
|
LockTZInfo;
|
|
info:=find_transition(timer,timerIsUTC,trans_start,trans_end);
|
|
GetLocalTimezone:=assigned(info);
|
|
if GetLocalTimezone then
|
|
DoGetLocalTimezone(info,trans_start,trans_end,ATZInfo);
|
|
UnlockTZInfo;
|
|
end;
|
|
|
|
function GetLocalTimezone(timer:int64;timerIsUTC:Boolean;var ATZInfo:TTZInfo;var ATZInfoEx:TTZInfoEx):Boolean;
|
|
var
|
|
info: pttinfo;
|
|
trans_start,trans_end,timerUTC: int64;
|
|
begin
|
|
{ check if time is in current global Tzinfo }
|
|
ATZInfo:=CurrentTZinfo[InterlockedExchangeAdd(CurrentTZindex, 0)];
|
|
if not timerIsUTC then
|
|
timerUTC:=timer-ATZInfo.seconds
|
|
else
|
|
timerUTC:=timer;
|
|
if (ATZInfo.validsince<=timerUTC) and (timerUTC<ATZInfo.validuntil) then
|
|
begin
|
|
ATZInfoEx:=TZInfoEx;
|
|
Exit(True);
|
|
end;
|
|
|
|
{ not current - search through all }
|
|
LockTZInfo;
|
|
info:=find_transition(timer,timerIsUTC,trans_start,trans_end);
|
|
GetLocalTimezone:=assigned(info);
|
|
if GetLocalTimezone then
|
|
begin
|
|
DoGetLocalTimezone(info,trans_start,trans_end,ATZInfo);
|
|
DoGetLocalTimezoneEx(timer,info,ATZInfoEx);
|
|
end;
|
|
UnlockTZInfo;
|
|
end;
|
|
|
|
procedure RefreshTZInfo;
|
|
var
|
|
NewTZInfo: TTZInfo;
|
|
NewTZInfoEx: TTZInfoEx;
|
|
begin
|
|
LockTZInfo;
|
|
if GetLocalTimezone(fptime,true,NewTZInfo,NewTZInfoEx) then
|
|
SetTZInfo(NewTZInfo,NewTZInfoEx);
|
|
UnlockTZInfo;
|
|
end;
|
|
|
|
Const
|
|
DefaultTimeZoneDir = '/usr/share/zoneinfo';
|
|
|
|
function TimeZoneDir : ShortString;
|
|
|
|
begin
|
|
// Observe TZDIR environment variable.
|
|
TimeZoneDir:=fpgetenv('TZDIR');
|
|
if TimeZoneDir='' then
|
|
TimeZoneDir:=DefaultTimeZoneDir;
|
|
if TimeZoneDir[length(TimeZoneDir)]<>'/' then
|
|
TimeZoneDir:=TimeZoneDir+'/';
|
|
end;
|
|
|
|
function ReadTimezoneFile(fn:string) : Boolean;
|
|
|
|
function decode(const l:longint):longint;
|
|
begin
|
|
{$IFDEF ENDIAN_LITTLE}
|
|
decode:=SwapEndian(l);
|
|
{$ELSE}
|
|
decode:=l;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function decode(const l:int64):int64;
|
|
begin
|
|
{$IFDEF ENDIAN_LITTLE}
|
|
decode:=SwapEndian(l);
|
|
{$ELSE}
|
|
decode:=l;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
const
|
|
bufsize = 2048;
|
|
var
|
|
buf : array[0..bufsize-1] of byte;
|
|
bufptr : pbyte;
|
|
bufbytes : tsSize;
|
|
bufoverflow : boolean;
|
|
f : longint;
|
|
tzhead : ttzhead;
|
|
|
|
function readfilebuf : TsSize;
|
|
begin
|
|
bufptr := @buf[0];
|
|
bufbytes:=fpread(f, buf, bufsize);
|
|
readfilebuf:=bufbytes;
|
|
end;
|
|
|
|
Procedure checkbufptr(asize : integer);
|
|
|
|
var
|
|
a : tssize;
|
|
|
|
begin
|
|
a:=bufptr-@buf+asize;
|
|
if (a>bufbytes) then
|
|
bufoverflow:=true;
|
|
end;
|
|
|
|
function readbufbyte: byte;
|
|
begin
|
|
if bufptr > @buf[bufsize-1] then
|
|
readfilebuf;
|
|
checkbufptr(1);
|
|
readbufbyte := bufptr^;
|
|
inc(bufptr);
|
|
end;
|
|
|
|
function readbuf(dest:pointer; count: integer): integer;
|
|
var
|
|
numbytes: integer;
|
|
begin
|
|
readbuf := 0;
|
|
repeat
|
|
numbytes := (@buf[bufsize-1] + 1) - bufptr;
|
|
if numbytes > count then
|
|
numbytes := count;
|
|
if numbytes > 0 then
|
|
begin
|
|
checkbufptr(numbytes);
|
|
if assigned(dest) then
|
|
move(bufptr^, dest^, numbytes);
|
|
inc(bufptr, numbytes);
|
|
dec(count, numbytes);
|
|
inc(readbuf, numbytes);
|
|
inc(dest, numbytes);
|
|
end;
|
|
if count > 0 then
|
|
readfilebuf
|
|
else
|
|
break;
|
|
until false;
|
|
end;
|
|
|
|
function readheader: boolean;
|
|
var
|
|
i : longint;
|
|
begin
|
|
i:=readbuf(@tzhead,sizeof(tzhead));
|
|
if i<>sizeof(tzhead) then
|
|
exit(False);
|
|
tzhead.tzh_timecnt:=decode(tzhead.tzh_timecnt);
|
|
tzhead.tzh_typecnt:=decode(tzhead.tzh_typecnt);
|
|
tzhead.tzh_charcnt:=decode(tzhead.tzh_charcnt);
|
|
tzhead.tzh_leapcnt:=decode(tzhead.tzh_leapcnt);
|
|
tzhead.tzh_ttisstdcnt:=decode(tzhead.tzh_ttisstdcnt);
|
|
tzhead.tzh_ttisgmtcnt:=decode(tzhead.tzh_ttisgmtcnt);
|
|
readheader:=(tzhead.tzh_identifier[0]='T') and (tzhead.tzh_identifier[1]='Z')
|
|
and (tzhead.tzh_identifier[2]='i') and (tzhead.tzh_identifier[3]='f');
|
|
end;
|
|
|
|
procedure AllocFields;
|
|
begin
|
|
num_transitions:=tzhead.tzh_timecnt;
|
|
num_types:=tzhead.tzh_typecnt;
|
|
num_leaps:=tzhead.tzh_leapcnt;
|
|
reallocmem(transitions,num_transitions*sizeof(int64));
|
|
reallocmem(type_idxs,num_transitions);
|
|
reallocmem(types,num_types*sizeof(tttinfo));
|
|
reallocmem(zone_names,tzhead.tzh_charcnt);
|
|
reallocmem(leaps,num_leaps*sizeof(tleap));
|
|
end;
|
|
|
|
function readdata: boolean;
|
|
var
|
|
i : longint;
|
|
longval: longint;
|
|
version: longint;
|
|
begin
|
|
if tzhead.tzh_version='2' then
|
|
begin
|
|
version:=2;
|
|
// skip version 0
|
|
readbuf(nil,
|
|
tzhead.tzh_timecnt*4 // transitions
|
|
+tzhead.tzh_timecnt // type_idxs
|
|
+tzhead.tzh_typecnt*6 // types
|
|
+tzhead.tzh_charcnt // zone_names
|
|
+tzhead.tzh_leapcnt*8 // leaps
|
|
+tzhead.tzh_ttisstdcnt // isstd
|
|
+tzhead.tzh_ttisgmtcnt // isgmt
|
|
);
|
|
readheader; // read version 2 header
|
|
if tzhead.tzh_version<>'2' then
|
|
Exit(False);
|
|
end
|
|
else
|
|
version:=0;
|
|
|
|
AllocFields;
|
|
|
|
if version=2 then
|
|
begin // read 64bit values
|
|
readbuf(transitions,num_transitions*sizeof(int64));
|
|
for i:=0 to num_transitions-1 do
|
|
transitions[i]:=decode(transitions[i]);
|
|
end
|
|
else
|
|
begin // read 32bit values
|
|
for i:=0 to num_transitions-1 do
|
|
begin
|
|
readbuf(@longval,sizeof(longval));
|
|
transitions[i]:=decode(longval);
|
|
end;
|
|
end;
|
|
readbuf(type_idxs,num_transitions);
|
|
|
|
for i:=0 to num_types-1 do
|
|
begin
|
|
readbuf(@types[i].offset,sizeof(LongInt));
|
|
types[i].offset:=decode(types[i].offset);
|
|
readbuf(@types[i].isdst,1);
|
|
readbuf(@types[i].idx,1);
|
|
types[i].isstd:=0;
|
|
types[i].isgmt:=0;
|
|
end;
|
|
|
|
readbuf(zone_names,tzhead.tzh_charcnt);
|
|
if version=2 then
|
|
begin // read 64bit values
|
|
for i:=0 to num_leaps-1 do
|
|
begin
|
|
readbuf(@leaps[i].transition,sizeof(int64));
|
|
readbuf(@leaps[i].change,sizeof(longint));
|
|
leaps[i].transition:=decode(leaps[i].transition);
|
|
leaps[i].change:=decode(leaps[i].change);
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
for i:=0 to num_leaps-1 do
|
|
begin
|
|
readbuf(@longval,sizeof(longval));
|
|
leaps[i].transition:=decode(longval);
|
|
readbuf(@longval,sizeof(longval));
|
|
leaps[i].change:=decode(longval);
|
|
end;
|
|
end;
|
|
|
|
for i:=0 to tzhead.tzh_ttisstdcnt-1 do
|
|
types[i].isstd:=byte(readbufbyte<>0);
|
|
|
|
for i:=0 to tzhead.tzh_ttisgmtcnt-1 do
|
|
types[i].isgmt:=byte(readbufbyte<>0);
|
|
|
|
readdata:=true;
|
|
end;
|
|
procedure ClearCurrentTZinfo;
|
|
var
|
|
i:integer;
|
|
begin
|
|
for i:=low(CurrentTZinfo) to high(CurrentTZinfo) do
|
|
CurrentTZinfo[i] := Default(TTZInfo);
|
|
end;
|
|
begin
|
|
if fn='' then
|
|
fn:='localtime';
|
|
if fn[1]<>'/' then
|
|
fn:='/usr/share/zoneinfo/'+fn;
|
|
f:=fpopen(fn,Open_RdOnly);
|
|
if f<0 then
|
|
exit(False);
|
|
bufoverflow:=false;
|
|
bufptr := @buf[bufsize-1]+1;
|
|
tzhead:=default(ttzhead);
|
|
LockTZInfo;
|
|
ReadTimezoneFile:=(readheader() and readdata()) and not BufOverflow;
|
|
ClearCurrentTZinfo;
|
|
UnlockTZInfo;
|
|
fpclose(f);
|
|
end;
|
|
|
|
Const
|
|
// Debian system; contains location of timezone file.
|
|
TimeZoneLocationFile = '/etc/timezone';
|
|
// SuSE has link in /usr/lib/zoneinfo/localtime to /etc/localtime
|
|
// RedHat uses /etc/localtime
|
|
|
|
TimeZoneFile = '/etc/localtime'; // POSIX
|
|
AltTimeZoneFile = '/usr/lib/zoneinfo/localtime'; // Other
|
|
iOSTimeZoneFile = '/var/db/timezone/localtime'; // iOS
|
|
{$ifdef BSD}
|
|
BSDTimeZonefile = DefaultTimeZoneDir; // BSD usually is POSIX
|
|
// compliant though
|
|
{$ENDIF}
|
|
|
|
{$ifndef FPC_HAS_GETTIMEZONEFILE}
|
|
|
|
function GetTimezoneFile:string;
|
|
var
|
|
f,len : longint;
|
|
fn,s : shortstring;
|
|
info : stat;
|
|
begin
|
|
GetTimezoneFile:='';
|
|
// Observe TZ variable.
|
|
fn:=fpgetenv('TZ');
|
|
if (fn<>'') then
|
|
if (fn[1]=':') then
|
|
begin
|
|
Delete(fn,1,1);
|
|
if (fn<>'') then
|
|
begin
|
|
if (fn[1]<>'/') then
|
|
Exit(TimeZoneDir+fn);
|
|
Exit(fn);
|
|
end;
|
|
end;
|
|
if (fn='') then
|
|
fn:=TimeZoneLocationFile;
|
|
f:=fpopen(TimeZoneLocationFile,Open_RdOnly);
|
|
if f>0 then
|
|
begin
|
|
len:=fpread(f,s[1],high(s));
|
|
s[0]:=chr(len);
|
|
len:=pos(#10,s);
|
|
if len<>0 then
|
|
s[0]:=chr(len-1);
|
|
fpclose(f);
|
|
GetTimezoneFile:=s;
|
|
end
|
|
// Try SuSE
|
|
else if fpstat(TimeZoneFile,{$ifdef oldlinuxstat}baseunix.stat(info){$else}info{$endif})>=0 then
|
|
GetTimeZoneFile:=TimeZoneFile
|
|
// Try RedHat
|
|
else If fpstat(AltTimeZoneFile,{$ifdef oldlinuxstat}baseunix.stat(info){$else}info{$endif})>=0 then
|
|
GetTimeZoneFile:=AltTimeZoneFile
|
|
{$ifdef BSD}
|
|
// else
|
|
// If fpstat(BSDTimeZoneFile,{$ifdef oldlinuxstat}baseunix.stat(info){$else}info{$endif})>=0 then
|
|
// GetTimeZoneFile:=BSDTimeZoneFile
|
|
{$ENDIF}
|
|
{$if (defined(darwin) and defined(arm)) or defined(iphonesim)}
|
|
else If fpstat(iOSTimeZoneFile,info)>=0 then
|
|
GetTimeZoneFile:=iOSTimeZoneFile
|
|
{$endif}
|
|
end;
|
|
|
|
{$endif ndef FPC_HAS_GETTIMEZONEFILE}
|
|
|
|
procedure InitLocalTime;
|
|
begin
|
|
ReadTimezoneFile(GetTimezoneFile);
|
|
RefreshTZInfo;
|
|
end;
|
|
|
|
|
|
procedure DoneLocalTime;
|
|
begin
|
|
if assigned(transitions) then
|
|
freemem(transitions);
|
|
transitions:=nil;
|
|
if assigned(type_idxs) then
|
|
freemem(type_idxs);
|
|
type_idxs:=nil;
|
|
if assigned(types) then
|
|
freemem(types);
|
|
types:=nil;
|
|
if assigned(zone_names) then
|
|
freemem(zone_names);
|
|
zone_names:=Nil;
|
|
if assigned(leaps) then
|
|
freemem(leaps);
|
|
leaps:=nil;
|
|
num_transitions:=0;
|
|
num_leaps:=0;
|
|
num_types:=0;
|
|
end;
|
|
|
|
Procedure ReReadLocalTime;
|
|
|
|
begin
|
|
LockTZInfo;
|
|
DoneLocalTime;
|
|
InitLocalTime;
|
|
UnlockTZInfo;
|
|
end;
|