* thread-safe time zone info read/write

git-svn-id: trunk@47309 -
This commit is contained in:
ondrej 2020-11-04 14:56:56 +00:00
parent c64429cdd0
commit 7fbceaac21
4 changed files with 184 additions and 78 deletions

View File

@ -41,10 +41,6 @@ var
ucal_inDaylightTime: function (cal: UCalendar; var status: UErrorCode): UBool; cdecl;
ucal_get: function (cal: UCalendar; field: UCalendarDateFields; var status: UErrorCode): int32_t; cdecl;
var
TZStandardName: utf8string;
TZDaylightName: utf8string;
GetIcuProc: function (const Name: AnsiString; var ProcPtr; libId: longint): boolean; external name 'ANDROID_GET_ICU_PROC';
procedure ReadTimeZoneFromICU;
@ -52,8 +48,12 @@ var
locale: utf8string;
tz: unicodestring;
res: unicodestring;
TZStandardName: utf8string;
TZDaylightName: utf8string;
err: UErrorCode;
cal: UCalendar;
lTZInfo: TTZInfo;
lTZInfoEx: TTZInfo;
begin
if not Assigned(GetIcuProc) then exit;
if not GetIcuProc('ucal_open', ucal_open, 1) then exit;
@ -68,25 +68,30 @@ begin
cal:=ucal_open(PUnicodeChar(tz), Length(tz), PAnsiChar(locale), 0, err);
if cal = nil then
exit;
Tzinfo.daylight:=ucal_inDaylightTime(cal, err);
lTzinfo.daylight:=ucal_inDaylightTime(cal, err);
SetLength(res, 200);
SetLength(res, ucal_getTimeZoneDisplayName(cal, UCAL_SHORT_STANDARD, PAnsiChar(locale), PUnicodeChar(res), Length(res), err));
TZStandardName:=utf8string(res);
Tzinfo.name[False]:=PAnsiChar(TZStandardName);
lTZInfoEx.name[False]:=TZStandardName;
SetLength(res, 200);
SetLength(res, ucal_getTimeZoneDisplayName(cal, UCAL_SHORT_DST, PAnsiChar(locale), PUnicodeChar(res), Length(res), err));
TZDaylightName:=utf8string(res);
Tzinfo.name[True]:=PAnsiChar(TZDaylightName);
lTZInfoEx.name[True]:=TZDaylightName;
Tzinfo.seconds:=ucal_get(cal, UCAL_ZONE_OFFSET, err) div 1000;
if Tzinfo.daylight then
Tzinfo.seconds:=Tzinfo.seconds + ucal_get(cal, UCAL_DST_OFFSET, err) div 1000;
lTZInfoEx.leap_correct:=0;
lTZInfoEx.leap_hit:=0;
lTZInfo.seconds:=ucal_get(cal, UCAL_ZONE_OFFSET, err) div 1000;
if lTZInfo.daylight then
lTZInfo.seconds:=Tzinfo.seconds + ucal_get(cal, UCAL_DST_OFFSET, err) div 1000;
// ToDo: correct validsince/validuntil values
Tzinfo.validsince:=low(Tzinfo.validsince);
Tzinfo.validuntil:=high(Tzinfo.validuntil);
lTZInfo.validsince:=low(lTZInfo.validsince);
lTZInfo.validuntil:=high(lTZInfo.validuntil);
SetTZInfo(lTZInfo, lTZInfoEx);
ucal_close(cal);
end;

View File

@ -1653,13 +1653,14 @@ begin
else
UnixTime:=LocalToEpoch(Year, Month, Day, Hour, Minute, Second);
{ check if time is in current global Tzinfo }
if (Tzinfo.validsince<UnixTime) and (UnixTime<Tzinfo.validuntil) then
lTzinfo:=Tzinfo;
if (lTzinfo.validsince<=UnixTime) and (UnixTime<lTzinfo.validuntil) then
begin
Result:=True;
Offset:=-TZInfo.seconds div 60;
Offset:=-lTZInfo.seconds div 60;
end else
begin
Result:=GetLocalTimezone(UnixTime,True,lTZInfo,False);
Result:=GetLocalTimezone(UnixTime,True,lTZInfo);
if Result then
Offset:=-lTZInfo.seconds div 60;
end;

View File

@ -75,64 +75,93 @@ begin
end;
function GetLocalTimezone(timer:cint;timerIsUTC:Boolean;var ATZInfo:TTZInfo;FullInfo:Boolean):Boolean;
var
info : pttinfo;
i,trans_start,trans_end : longint;
procedure DoGetLocalTimezone(info:pttinfo;const trans_start,trans_end:longint;var ATZInfo:TTZInfo);
begin
{ reset }
ATZInfo.Daylight:=false;
ATZInfo.Seconds:=0;
ATZInfo.Name[false]:=nil;
ATZInfo.Name[true]:=nil;
ATZInfo.validsince:=0;
ATZInfo.validuntil:=0;
ATZInfo.leap_correct:=0;
ATZInfo.leap_hit:=0;
{ get info }
info:=find_transition(timer,timerIsUTC,trans_start,trans_end);
GetLocalTimezone:=assigned(info);
if not GetLocalTimezone then
exit;
ATZInfo.validsince:=trans_start;
ATZInfo.validuntil:=trans_end;
ATZInfo.Daylight:=info^.isdst;
ATZInfo.Seconds:=info^.offset;
if not FullInfo then
Exit;
end;
procedure DoGetLocalTimezoneEx(timer:cint;info:pttinfo;var ATZInfoEx:TTZInfoEx);
var
i : longint;
names: array[Boolean] of pchar;
begin
names[true]:=nil;
names[false]:=nil;
ATZInfoEx.leap_hit:=0;
ATZInfoEx.leap_correct:=0;
i:=0;
while (i<num_types) do
begin
ATZInfo.name[types[i].isdst]:=@zone_names[types[i].idx];
names[types[i].isdst]:=@zone_names[types[i].idx];
inc(i);
end;
ATZInfo.name[info^.isdst]:=@zone_names[info^.idx];
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);
ATZInfo.leap_correct:=leaps[i].change;
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
ATZInfo.leap_hit:=1;
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(ATZInfo.leap_hit);
inc(ATZInfoEx.leap_hit);
dec(i);
end;
end;
end;
procedure GetLocalTimezone(timer:cint;timerIsUTC:Boolean);
function GetLocalTimezone(timer:cint;timerIsUTC:Boolean;var ATZInfo:TTZInfo):Boolean;
var
info: pttinfo;
trans_start,trans_end: longint;
begin
GetLocalTimezone(timer,timerIsUTC,Tzinfo,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:cint;timerIsUTC:Boolean;var ATZInfo:TTZInfo;var ATZInfoEx:TTZInfoEx):Boolean;
var
info: pttinfo;
trans_start,trans_end: longint;
begin
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,false,NewTZInfo,NewTZInfoEx) then
SetTZInfo(NewTZInfo,NewTZInfoEx);
UnlockTZInfo;
end;
Const
@ -352,7 +381,7 @@ end;
procedure InitLocalTime;
begin
ReadTimezoneFile(GetTimezoneFile);
GetLocalTimezone(fptime,false);
RefreshTZInfo;
end;
@ -381,6 +410,8 @@ end;
Procedure ReReadLocalTime;
begin
LockTZInfo;
DoneLocalTime;
InitLocalTime;
UnlockTZInfo;
end;

View File

@ -56,23 +56,27 @@ Const
type
TTZInfo = record
daylight : boolean;
name : array[boolean] of pchar;
seconds : Longint; // difference from UTC
validsince : int64; // UTC timestamp
validuntil : int64; // UTC timestamp
end;
TTZInfoEx = record
name : array[boolean] of RawByteString; { False = StandardName, True = DaylightName }
leap_correct : longint;
leap_hit : longint;
end;
var
Tzinfo : TTZInfo;
Function GetTzseconds : Longint;
property Tzseconds : Longint read GetTzseconds;
function Gettzdaylight : boolean;
function Gettzname(const b : boolean) : pchar;
property tzdaylight : boolean read Gettzdaylight;
property tzname[b : boolean] : pchar read Gettzname;
function Gettzname(const b : boolean) : string;
property tzname[b : boolean] : string read Gettzname;
function GetTZInfo : TTZInfo;
property TZInfo : TTZInfo read GetTZInfo;
function GetTZInfoEx : TTZInfoEx;
property TZInfoEx : TTZInfoEx read GetTZInfoEx;
procedure SetTZInfo(const ATZInfo: TTZInfo; const ATZInfoEx: TTZInfoEx);
{************ Procedure/Functions ************}
@ -84,14 +88,14 @@ var
// it doesn't (yet) work for.
{ timezone support }
function GetLocalTimezone(timer:cint;timerIsUTC:Boolean;var ATZInfo:TTZInfo;FullInfo:Boolean):Boolean;
procedure GetLocalTimezone(timer:cint;timerIsUTC:Boolean);
function GetLocalTimezone(timer:cint;timerIsUTC:Boolean;var ATZInfo:TTZInfo;var ATZInfoEx:TTZInfoEx):Boolean;
function GetLocalTimezone(timer:cint;timerIsUTC:Boolean;var ATZInfo:TTZInfo):Boolean;
procedure RefreshTZInfo;
procedure ReadTimezoneFile(fn:string);
function GetTimezoneFile:string;
Procedure ReReadLocalTime;
{$ENDIF}
Procedure RefreshTZInfoIfNeeded;
Function UniversalToEpoch(year,month,day,hour,minute,second:Word):int64; // use DateUtils.DateTimeToUnix for cross-platform applications
Function LocalToEpoch(year,month,day,hour,minute,second:Word):int64; // use DateUtils.DateTimeToUnix for cross-platform applications
Procedure EpochToLocal(epoch:int64;var year,month,day,hour,minute,second:Word); // use DateUtils.UnixToDateTime for cross-platform applications
@ -187,6 +191,31 @@ Function getenv(name:string):Pchar; external name 'FPC_SYSC_FPGETENV';
timezone support
******************************************************************************}
var
CurrentTZinfo : array [0..1] of TTZInfo;
CurrentTzinfoEx : array [0..1] of TTZInfoEx;
CurrentTZindex : LongInt = 0; // current index for CurrentTZinfo/CurrentTZinfoEx - can be only 0 or 1
{$ifdef FPC_HAS_FEATURE_THREADING}
UseTZThreading: Boolean = false;
TZInfoCS: TRTLCriticalSection;
{$endif}
procedure LockTZInfo;
begin
{$if declared(UseTZThreading)}
if UseTZThreading then
EnterCriticalSection(TZInfoCS);
{$endif}
end;
procedure UnlockTZInfo;
begin
{$if declared(UseTZThreading)}
if UseTZThreading then
LeaveCriticalSection(TZInfoCS);
{$endif}
end;
Function GetTzseconds : Longint;
begin
GetTzseconds:=Tzinfo.seconds;
@ -197,9 +226,43 @@ begin
Gettzdaylight:=Tzinfo.daylight;
end;
function Gettzname(const b : boolean) : pchar;
function Gettzname(const b : boolean) : string;
begin
Gettzname:=Tzinfo.name[b];
Gettzname:=TzinfoEx.name[b];
end;
function GetTZInfo : TTZInfo;
var
curtime: time_t;
begin
GetTZInfo:=CurrentTZinfo[InterlockedExchangeAdd(CurrentTZindex, 0)];
curtime:=fptime;
if not((GetTZInfo.validsince+GetTZInfo.seconds<=curtime) and (curtime<GetTZInfo.validuntil+GetTZInfo.seconds)) then
begin
RefreshTZInfo;
GetTZInfo:=CurrentTZinfo[InterlockedExchangeAdd(CurrentTZindex, 0)];
end;
end;
function GetTZInfoEx : TTZInfoEx;
begin
GetTZInfoEx:=CurrentTzinfoEx[InterlockedExchangeAdd(CurrentTZindex, 0)];
end;
procedure SetTZInfo(const ATZInfo: TTZInfo; const ATZInfoEx: TTZInfoEx);
var
OldTZindex,NewTZindex: longint;
begin
LockTZInfo;
OldTZindex:=InterlockedExchangeAdd(CurrentTZindex,0);
if OldTZindex=0 then
NewTZindex:=1
else
NewTZindex:=0;
CurrentTzinfo[NewTZindex]:=ATZInfo;
CurrentTzinfoEx[NewTZindex]:=ATZInfoEx;
InterlockedExchangeAdd(CurrentTZindex,NewTZindex-OldTZindex);
UnlockTZInfo;
end;
Const
@ -235,20 +298,22 @@ Procedure EpochToLocal(epoch:Int64;var year,month,day,hour,minute,second:Word);
Transforms Epoch time into local time (hour, minute,seconds)
}
Var
DateNum: LongInt;
lTZInfo: TTZInfo;
lseconds: LongInt;
Begin
{ check if time is in current global Tzinfo }
if (Tzinfo.validsince<epoch) and (epoch<Tzinfo.validuntil) then
inc(Epoch,TZInfo.seconds)
lTZInfo:=TZInfo;
lseconds:=lTZInfo.seconds;
if (lTZInfo.validsince<=epoch) and (epoch<lTZInfo.validuntil) then
inc(Epoch,lseconds)
else
begin
{$if declared(GetLocalTimezone)}
if GetLocalTimezone(epoch,true,lTZInfo,false) then
if GetLocalTimezone(epoch,true,lTZInfo) then
inc(Epoch,lTZInfo.seconds)
else { fallback }
{$endif}
inc(Epoch,TZInfo.seconds);
inc(Epoch,lseconds);
end;
EpochToUniversal(epoch,year,month,day,hour,minute,second);
@ -278,19 +343,22 @@ Function LocalToEpoch(year,month,day,hour,minute,second:Word):Int64;
Var
lTZInfo: TTZInfo;
UniversalEpoch: Int64;
lseconds: LongInt;
Begin
UniversalEpoch:=UniversalToEpoch(year,month,day,hour,minute,second);
{ check if time is in current global Tzinfo }
if (Tzinfo.validsince<UniversalEpoch-Tzinfo.seconds) and (UniversalEpoch-Tzinfo.seconds<Tzinfo.validuntil) then
LocalToEpoch:=UniversalEpoch-TZInfo.seconds
lTZInfo:=TZInfo;
lseconds:=lTZInfo.seconds;
if (lTZInfo.validsince<=UniversalEpoch-lTZInfo.seconds) and (UniversalEpoch-lTZInfo.seconds<lTZInfo.validuntil) then
LocalToEpoch:=UniversalEpoch-lseconds
else
begin
{$if declared(GetLocalTimezone)}
if GetLocalTimezone(UniversalEpoch,false,lTZInfo,false) then
if GetLocalTimezone(UniversalEpoch,false,lTZInfo) then
LocalToEpoch:=UniversalEpoch-lTZInfo.seconds
else { fallback }
{$endif}
LocalToEpoch:=UniversalEpoch-TZInfo.seconds
LocalToEpoch:=UniversalEpoch-lseconds;
end;
End;
@ -319,20 +387,6 @@ Begin
GregorianToJulian:=((((Month*153)+2) div 5)+Day)+D2+XYear+Century;
End;
Procedure RefreshTZInfoIfNeeded;
{$if declared(ReReadLocalTime)}
var
curtime: time_t;
begin
curtime:=fptime;
if ((curtime<Tzinfo.validsince+Tzinfo.seconds) or (curtime>Tzinfo.validuntil+Tzinfo.seconds)) then
GetLocalTimezone(fptime,false);
end;
{$else}
begin
end;
{$endif}
{******************************************************************************
Process related calls
******************************************************************************}
@ -1408,7 +1462,18 @@ end;
{$I unixandroid.inc}
{$endif android}
{$if declared(UseTZThreading)}
procedure InitTZThreading;
begin
UseTZThreading:=True;
InitCriticalSection(TZInfoCS);
end;
{$endif}
Initialization
{$if declared(UseTZThreading)}
RegisterLazyInitThreadingProc(@InitTZThreading);
{$endif}
{$IFNDEF DONT_READ_TIMEZONE}
InitLocalTime;
{$endif}
@ -1420,4 +1485,8 @@ finalization
{$IFNDEF DONT_READ_TIMEZONE}
DoneLocalTime;
{$endif}
{$if declared(UseTZThreading)}
if UseTZThreading then
DoneCriticalSection(TZInfoCS);
{$endif}
End.