LocalToEpoch, EpochToLocal: fix for arbitrary datetime

git-svn-id: trunk@47291 -
This commit is contained in:
ondrej 2020-11-03 14:27:07 +00:00
parent 282aa0daa7
commit f4cff81881
7 changed files with 180 additions and 267 deletions

View File

@ -14,7 +14,7 @@ interface
uses
{$IFDEF MSWINDOWS}windows,{$ENDIF}
{$IFDEF UNIX}unixutil,{$ENDIF}
{$IFDEF UNIX}unix,{$ENDIF}
sysutils,
dateutils,
math,

View File

@ -57,7 +57,7 @@ Function AddDisk(const path:string) : byte; platform;
Implementation
Uses
UnixUtil, // tzSeconds
UnixUtil,
Strings,
Unix,
{$ifdef FPC_USE_LIBC}initc{$ELSE}Syscall{$ENDIF};
@ -89,90 +89,6 @@ type
--- Info / Date / Time ---
******************************************************************************}
Const
{Date Calculation}
C1970 = 2440588;
D0 = 1461;
D1 = 146097;
D2 = 1721119;
type
GTRec = packed Record
Year,
Month,
MDay,
WDay,
Hour,
Minute,
Second : Word;
End;
Function GregorianToJulian(Year,Month,Day:Longint):LongInt;
Var
Century,XYear: LongInt;
Begin
If Month<=2 Then
Begin
Dec(Year);
Inc(Month,12);
End;
Dec(Month,3);
Century:=(longint(Year Div 100)*D1) shr 2;
XYear:=(longint(Year Mod 100)*D0) shr 2;
GregorianToJulian:=((((Month*153)+2) div 5)+Day)+D2+XYear+Century;
End;
Function LocalToEpoch(year,month,day,hour,minute,second:Word):Longint;
{
Transforms local time (year,month,day,hour,minutes,second) to Epoch time
(seconds since 00:00, january 1 1970, corrected for local time zone)
}
Begin
LocalToEpoch:=((GregorianToJulian(Year,Month,Day)-c1970)*86400)+
(LongInt(Hour)*3600)+(Longint(Minute)*60)+Second-TZSeconds;
End;
Procedure JulianToGregorian(JulianDN:LongInt;Var Year,Month,Day:Word);
Var
YYear,XYear,Temp,TempMonth : LongInt;
Begin
Temp:=((JulianDN-D2) shl 2)-1;
JulianDN:=Temp Div D1;
XYear:=(Temp Mod D1) or 3;
YYear:=(XYear Div D0);
Temp:=((((XYear mod D0)+4) shr 2)*5)-3;
Day:=((Temp Mod 153)+5) Div 5;
TempMonth:=Temp Div 153;
If TempMonth>=10 Then
Begin
inc(YYear);
dec(TempMonth,12);
End;
inc(TempMonth,3);
Month := TempMonth;
Year:=YYear+(JulianDN*100);
end;
Procedure EpochToLocal(epoch:longint;var year,month,day,hour,minute,second:Word);
{
Transforms Epoch time into local time (hour, minute,seconds)
}
Var
DateNum: LongInt;
Begin
inc(Epoch,TZSeconds);
Datenum:=(Epoch Div 86400) + c1970;
JulianToGregorian(DateNum,Year,Month,day);
Epoch:=Abs(Epoch Mod 86400);
Hour:=Epoch Div 3600;
Epoch:=Epoch Mod 3600;
Minute:=Epoch Div 60;
Second:=Epoch Mod 60;
End;
Function DosVersion:Word;
Var
Buffer : Array[0..255] of Char;

View File

@ -293,55 +293,6 @@ procedure UnhookSignal(RtlSigNum: Integer; OnlyIfHooked: Boolean = True);
{ Include SysCreateGUID function }
{$i suuid.inc}
Const
{Date Translation}
C1970=2440588;
D0 = 1461;
D1 = 146097;
D2 =1721119;
Procedure JulianToGregorian(JulianDN:LongInt;Var Year,Month,Day:Word);
Var
YYear,XYear,Temp,TempMonth : LongInt;
Begin
Temp:=((JulianDN-D2) shl 2)-1;
JulianDN:=Temp Div D1;
XYear:=(Temp Mod D1) or 3;
YYear:=(XYear Div D0);
Temp:=((((XYear mod D0)+4) shr 2)*5)-3;
Day:=((Temp Mod 153)+5) Div 5;
TempMonth:=Temp Div 153;
If TempMonth>=10 Then
Begin
inc(YYear);
dec(TempMonth,12);
End;
inc(TempMonth,3);
Month := TempMonth;
Year:=YYear+(JulianDN*100);
end;
Procedure EpochToLocal(epoch:longint;var year,month,day,hour,minute,second:Word);
{
Transforms Epoch time into local time (hour, minute,seconds)
}
Var
DateNum: LongInt;
Begin
inc(Epoch,TZSeconds);
Datenum:=(Epoch Div 86400) + c1970;
JulianToGregorian(DateNum,Year,Month,day);
Epoch:=Abs(Epoch Mod 86400);
Hour:=Epoch Div 3600;
Epoch:=Epoch Mod 3600;
Minute:=Epoch Div 60;
Second:=Epoch Mod 60;
End;
function GetTickCount64: QWord;
var
tp: TTimeVal;
@ -1674,7 +1625,6 @@ function GetLocalTimeOffset(const DateTime: TDateTime; out Offset: Integer): Boo
var
Year, Month, Day, Hour, Minute, Second, MilliSecond: word;
UnixTime: Int64;
lc,lh: cint;
lTZInfo: TTZInfo;
begin
DecodeDate(DateTime, Year, Month, Day);
@ -1687,7 +1637,7 @@ begin
Offset:=-TZInfo.seconds div 60;
end else
begin
Result:=GetLocalTimezone(UnixTime,lTZInfo,False);
Result:=GetLocalTimezone(UnixTime,True,lTZInfo,False);
if Result then
Offset:=-lTZInfo.seconds div 60;
end;

View File

@ -41,7 +41,7 @@ var
zone_names : pchar = Nil;
leaps : pleap = Nil;
function find_transition(timer:longint;var trans_start,trans_end:longint):pttinfo;
function find_transition(timer:longint;timerIsUTC:Boolean;var trans_start,trans_end:longint):pttinfo;
var
i : longint;
begin
@ -61,8 +61,10 @@ begin
i:=1;
while i<=num_transitions-1 do
begin
if (timer<transitions[i]) then
break;
case timerIsUTC of
True: if (timer<transitions[i]) then break;
False: if (timer<transitions[i]+types[type_idxs[i]].offset) then break;
end;
inc(i);
end;
trans_start:=transitions[i-1];
@ -73,7 +75,7 @@ begin
end;
function GetLocalTimezone(timer:cint;var ATZInfo:TTZInfo;FullInfo:Boolean):Boolean;
function GetLocalTimezone(timer:cint;timerIsUTC:Boolean;var ATZInfo:TTZInfo;FullInfo:Boolean):Boolean;
var
info : pttinfo;
i,trans_start,trans_end : longint;
@ -88,7 +90,7 @@ begin
ATZInfo.leap_correct:=0;
ATZInfo.leap_hit:=0;
{ get info }
info:=find_transition(timer,trans_start,trans_end);
info:=find_transition(timer,timerIsUTC,trans_start,trans_end);
GetLocalTimezone:=assigned(info);
if not GetLocalTimezone then
exit;
@ -128,9 +130,9 @@ begin
end;
procedure GetLocalTimezone(timer:cint);
procedure GetLocalTimezone(timer:cint;timerIsUTC:Boolean);
begin
GetLocalTimezone(timer,Tzinfo,true);
GetLocalTimezone(timer,timerIsUTC,Tzinfo,true);
end;
Const
@ -349,9 +351,8 @@ end;
procedure InitLocalTime;
begin
ReloadTzinfo:=@InitLocalTime;
ReadTimezoneFile(GetTimezoneFile);
GetLocalTimezone(fptime);
GetLocalTimezone(fptime,false);
end;

View File

@ -16,9 +16,8 @@ Unit Unix;
Interface
Uses
BaseUnix,UnixType,
UnixUtil // tzseconds
;
BaseUnix,UnixType;
// If you deprecated new symbols, please annotate the version.
// this makes it easier to decide if they can already be removed.
@ -54,6 +53,22 @@ Const
{** Time/Date Handling **}
type
TTZInfo = record
daylight : boolean;
name : array[boolean] of pchar;
seconds : Longint; // difference from UTC
validsince : int64; // UTC timestamp
validuntil : int64; // UTC timestamp
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;
@ -69,13 +84,21 @@ Const
// it doesn't (yet) work for.
{ timezone support }
function GetLocalTimezone(timer:cint;var ATZInfo:TTZInfo;FullInfo:Boolean):Boolean;
procedure GetLocalTimezone(timer:cint);
function GetLocalTimezone(timer:cint;timerIsUTC:Boolean;var ATZInfo:TTZInfo;FullInfo:Boolean):Boolean;
procedure GetLocalTimezone(timer:cint;timerIsUTC:Boolean);
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
Procedure EpochToUniversal(epoch:int64;var year,month,day,hour,minute,second:Word); // use DateUtils.UnixToDateTime for cross-platform applications
Procedure JulianToGregorian(JulianDN:LongInt;Var Year,Month,Day:Word); // use DateUtils.DateTimetoJulianDate for cross-platform applications
Function GregorianToJulian(Year,Month,Day:Longint):LongInt; // use DateUtils.JulianDateToDateTime for cross-platform applications
{** Process Handling **}
function FpExecLE (Const PathName:RawByteString;const S:Array Of RawByteString;MyEnv:ppchar):cint;
@ -164,6 +187,11 @@ Function getenv(name:string):Pchar; external name 'FPC_SYSC_FPGETENV';
timezone support
******************************************************************************}
Function GetTzseconds : Longint;
begin
GetTzseconds:=Tzinfo.seconds;
end;
function Gettzdaylight : boolean;
begin
Gettzdaylight:=Tzinfo.daylight;
@ -174,6 +202,137 @@ begin
Gettzname:=Tzinfo.name[b];
end;
Const
{Date Translation}
C1970=2440588;
D0 = 1461;
D1 = 146097;
D2 =1721119;
Procedure JulianToGregorian(JulianDN:LongInt;Var Year,Month,Day:Word);
Var
YYear,XYear,Temp,TempMonth : LongInt;
Begin
Temp:=((JulianDN-D2) shl 2)-1;
JulianDN:=Temp Div D1;
XYear:=(Temp Mod D1) or 3;
YYear:=(XYear Div D0);
Temp:=((((XYear mod D0)+4) shr 2)*5)-3;
Day:=((Temp Mod 153)+5) Div 5;
TempMonth:=Temp Div 153;
If TempMonth>=10 Then
Begin
inc(YYear);
dec(TempMonth,12);
End;
inc(TempMonth,3);
Month := TempMonth;
Year:=YYear+(JulianDN*100);
end;
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;
Begin
{ check if time is in current global Tzinfo }
if (Tzinfo.validsince<epoch) and (epoch<Tzinfo.validuntil) then
inc(Epoch,TZInfo.seconds)
else
begin
{$if declared(GetLocalTimezone)}
if GetLocalTimezone(epoch,true,lTZInfo,false) then
inc(Epoch,lTZInfo.seconds)
else { fallback }
{$endif}
inc(Epoch,TZInfo.seconds);
end;
EpochToUniversal(epoch,year,month,day,hour,minute,second);
End;
Procedure EpochToUniversal(epoch:Int64;var year,month,day,hour,minute,second:Word);
{
Transforms Epoch time into universal time (hour, minute,seconds)
}
Var
DateNum: LongInt;
Begin
Datenum:=(Epoch Div 86400) + c1970;
JulianToGregorian(DateNum,Year,Month,day);
Epoch:=Abs(Epoch Mod 86400);
Hour:=Epoch Div 3600;
Epoch:=Epoch Mod 3600;
Minute:=Epoch Div 60;
Second:=Epoch Mod 60;
End;
Function LocalToEpoch(year,month,day,hour,minute,second:Word):Int64;
{
Transforms local time (year,month,day,hour,minutes,second) to Epoch time
(seconds since 00:00, january 1 1970, corrected for local time zone)
}
Var
lTZInfo: TTZInfo;
UniversalEpoch: Int64;
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
else
begin
{$if declared(GetLocalTimezone)}
if GetLocalTimezone(LocalToEpoch,false,lTZInfo,false) then
LocalToEpoch:=UniversalEpoch-lTZInfo.seconds
else { fallback }
{$endif}
LocalToEpoch:=UniversalEpoch-TZInfo.seconds
end;
End;
Function UniversalToEpoch(year,month,day,hour,minute,second:Word):Int64;
{
Transforms universal time (year,month,day,hour,minutes,second) to Epoch time
(seconds since 00:00, january 1 1970, corrected for local time zone)
}
Begin
UniversalToEpoch:=(Int64(GregorianToJulian(Year,Month,Day)-c1970)*86400)+
(LongInt(Hour)*3600)+(Longint(Minute)*60)+Second;
End;
Function GregorianToJulian(Year,Month,Day:Longint):LongInt;
Var
Century,XYear: LongInt;
Begin
If Month<=2 Then
Begin
Dec(Year);
Inc(Month,12);
End;
Dec(Month,3);
Century:=(longint(Year Div 100)*D1) shr 2;
XYear:=(longint(Year Mod 100)*D0) shr 2;
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
******************************************************************************}
@ -446,6 +605,7 @@ end;
{$IFNDEF DONT_READ_TIMEZONE}
{ Include timezone handling routines which use /usr/share/timezone info }
{$i timezone.inc}
{$endif}
{******************************************************************************
FileSystem calls

View File

@ -29,45 +29,12 @@ interface
uses BaseUnix;
type
TTZInfo = record
daylight : boolean;
name : array[boolean] of pchar;
seconds : Longint; // difference from UTC
validsince : int64; // UTC timestamp
validuntil : int64; // UTC timestamp
leap_correct : longint;
leap_hit : longint;
end;
var
Tzinfo : TTZInfo;
ReloadTzinfo : TProcedure;
Function GetTzseconds : Longint;
property Tzseconds : Longint read GetTzseconds;
Function StringToPPChar(S: PChar;ReserveEntries:integer):ppchar;
Function StringToPPChar(Var S:RawByteString;ReserveEntries:integer):ppchar;
function ArrayStringToPPchar(const S:Array of RawByteString;reserveentries:Longint):ppchar; // const ?
Function LocalToEpoch(year,month,day,hour,minute,second:Word):int64; deprecated 'use DateUtils.DateTimeToUnix';
Procedure EpochToLocal(epoch:int64;var year,month,day,hour,minute,second:Word); deprecated 'use DateUtils.UnixToDateTime';
Procedure EpochToUniversal(epoch:int64;var year,month,day,hour,minute,second:Word); deprecated 'use DateUtils.UnixToDateTime';
Procedure JulianToGregorian(JulianDN:LongInt;Var Year,Month,Day:Word); deprecated 'use DateUtils.DateTimetoJulianDate';
Function GregorianToJulian(Year,Month,Day:Longint):LongInt; deprecated 'use DateUtils.JulianDateToDateTime';
implementation
Function GetTzseconds : Longint;
var
curtime: time_t;
begin
curtime:=fptime;
if assigned(ReloadTzinfo) and ((curtime<Tzinfo.validsince+Tzinfo.seconds) or (curtime>Tzinfo.validuntil+Tzinfo.seconds)) then
ReloadTzinfo;
GetTzseconds:=Tzinfo.seconds;
end;
function ArrayStringToPPchar(const S:Array of RawByteString;reserveentries:Longint):ppchar; // const ?
// Extra allocate reserveentries pchar's at the beginning (default param=0 after 1.0.x ?)
// Note: for internal use by skilled programmers only
@ -174,85 +141,4 @@ begin
end;
end;
Const
{Date Translation}
C1970=2440588;
D0 = 1461;
D1 = 146097;
D2 =1721119;
Procedure JulianToGregorian(JulianDN:LongInt;Var Year,Month,Day:Word);
Var
YYear,XYear,Temp,TempMonth : LongInt;
Begin
Temp:=((JulianDN-D2) shl 2)-1;
JulianDN:=Temp Div D1;
XYear:=(Temp Mod D1) or 3;
YYear:=(XYear Div D0);
Temp:=((((XYear mod D0)+4) shr 2)*5)-3;
Day:=((Temp Mod 153)+5) Div 5;
TempMonth:=Temp Div 153;
If TempMonth>=10 Then
Begin
inc(YYear);
dec(TempMonth,12);
End;
inc(TempMonth,3);
Month := TempMonth;
Year:=YYear+(JulianDN*100);
end;
Procedure EpochToLocal(epoch:Int64;var year,month,day,hour,minute,second:Word);
{
Transforms Epoch time into local time (hour, minute,seconds)
}
Var
DateNum: LongInt;
Begin
inc(Epoch,TZSeconds);
EpochToUniversal(epoch,year,month,day,hour,minute,second);
End;
Procedure EpochToUniversal(epoch:Int64;var year,month,day,hour,minute,second:Word);
{
Transforms Epoch time into universal time (hour, minute,seconds)
}
Var
DateNum: LongInt;
Begin
Datenum:=(Epoch Div 86400) + c1970;
JulianToGregorian(DateNum,Year,Month,day);
Epoch:=Abs(Epoch Mod 86400);
Hour:=Epoch Div 3600;
Epoch:=Epoch Mod 3600;
Minute:=Epoch Div 60;
Second:=Epoch Mod 60;
End;
Function LocalToEpoch(year,month,day,hour,minute,second:Word):Int64;
{
Transforms local time (year,month,day,hour,minutes,second) to Epoch time
(seconds since 00:00, january 1 1970, corrected for local time zone)
}
Begin
LocalToEpoch:=(Int64(GregorianToJulian(Year,Month,Day)-c1970)*86400)+
(LongInt(Hour)*3600)+(Longint(Minute)*60)+Second-TZSeconds;
End;
Function GregorianToJulian(Year,Month,Day:Longint):LongInt;
Var
Century,XYear: LongInt;
Begin
If Month<=2 Then
Begin
Dec(Year);
Inc(Month,12);
End;
Dec(Month,3);
Century:=(longint(Year Div 100)*D1) shr 2;
XYear:=(longint(Year Mod 100)*D0) shr 2;
GregorianToJulian:=((((Month*153)+2) div 5)+Day)+D2+XYear+Century;
End;
end.

View File

@ -363,7 +363,7 @@ end;
// unix
,Errors, UnixUtil;
,Errors, Unix;
function LStrError(const Ernum: Longint; const UseUTF8: Boolean = False): string;
begin
@ -461,7 +461,7 @@ end;
function TZSeconds: Integer; inline;
begin
Result := unixutil.TZSeconds;
Result := unix.TZSeconds;
end;
{$ENDIF}