mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-04-24 11:29:16 +02:00
* thread-safe time zone info read/write
git-svn-id: trunk@47309 -
This commit is contained in:
parent
c64429cdd0
commit
7fbceaac21
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
137
rtl/unix/unix.pp
137
rtl/unix/unix.pp
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user