Browse Source

* Minimal TTimeZone class for Delphi compatibility

Michaël Van Canneyt 5 months ago
parent
commit
e062a54716
1 changed files with 281 additions and 10 deletions
  1. 281 10
      packages/rtl-objpas/src/inc/dateutil.inc

+ 281 - 10
packages/rtl-objpas/src/inc/dateutil.inc

@@ -25,10 +25,10 @@ interface
 {$ifndef FPUNONE}
 {$ifndef FPUNONE}
 {$IFDEF FPC_DOTTEDUNITS}
 {$IFDEF FPC_DOTTEDUNITS}
 uses
 uses
-  System.SysUtils, System.Math;
+  System.SysUtils, System.Math, System.TimeSpan;
 {$ELSE}
 {$ELSE}
 uses
 uses
-  SysUtils, Math;
+  SysUtils, Math, System.TimeSpan;
 {$ENDIF}
 {$ENDIF}
 
 
 { ---------------------------------------------------------------------
 { ---------------------------------------------------------------------
@@ -592,19 +592,64 @@ type
       Int64): Boolean; inline;
       Int64): Boolean; inline;
   end;
   end;
 
 
+type
+  ELocalTimeInvalid = class(Exception);
+  EDateTimeException = class(Exception);
+
+  TLocalTimeType = (lttStandard, lttDaylight, lttAmbiguous, lttInvalid);
+
+  TTimeZone = class abstract
+  private
+    class var _Local: TTimeZone;
+    function GetUtcOffsetInSeconds(const aDateTime: TDateTime; const aForceDaylight: Boolean): Int64;
+    function GetCurrentAbbreviation: string;
+    function GetCurrentDisplayName: string;
+    function GetCurrentUtcOffset: TTimeSpan;
+  protected
+    function DoGetID: string; virtual; abstract;
+    function DoGetDisplayName(const aDateTime: TDateTime; const aForceDaylight: Boolean): string; virtual; abstract;
+    procedure DoGetOffsetsAndType(const aDateTime: TDateTime; out aOffset, aDstSave: Int64; out aType: TLocalTimeType); virtual; abstract;
+  public
+    class constructor Init;
+    class destructor Done;
+    function GetUtcOffset(const aDateTime: TDateTime; const aForceDaylight: Boolean = False): TTimeSpan;
+    function ToUniversalTime(const aDateTime: TDateTime; const aForceDaylight: Boolean = False): TDateTime;
+    function ToLocalTime(const aDateTime: TDateTime): TDateTime;
+    function GetDisplayName(const aDateTime: TDateTime; const aForceDaylight: Boolean = False): string;
+    function GetAbbreviation(const aDateTime: TDateTime; const aForceDaylight: Boolean = False): string;
+    function GetLocalTimeType(const aDateTime: TDateTime): TLocalTimeType; inline;
+    function HasDST(const AYear: TDateTime): Boolean; overload;
+    function HasDST: Boolean; overload;
+    function IsStandardTime(const aDateTime: TDateTime; const aForceDaylight: Boolean = False): Boolean;
+    function IsInvalidTime(const aDateTime: TDateTime): Boolean;
+    function IsAmbiguousTime(const aDateTime: TDateTime): Boolean;
+    function IsDaylightTime(const aDateTime: TDateTime; const aForceDaylight: Boolean = False): Boolean;
+    { Returns the time zone identification string: e.g. Europe/Brussels }
+    property ID: string read DoGetID;
+    property DisplayName: string read GetCurrentDisplayName;
+    property Abbreviation: string read GetCurrentAbbreviation;
+    property UtcOffset: TTimeSpan read GetCurrentUtcOffset;
+    class property Local: TTimeZone read _Local;
+  end;
 
 
 implementation
 implementation
 
 
 {$IFDEF FPC_DOTTEDUNITS}
 {$IFDEF FPC_DOTTEDUNITS}
-uses System.SysConst;
+uses
+  {$IFDEF UNIX} UnixApi.Unix, {$ENDIF}
+  {$IFDEF WINDOWS} WinApi.Windows, {$ENDIF}
+  System.SysConst;
 {$ELSE FPC_DOTTEDUNITS}
 {$ELSE FPC_DOTTEDUNITS}
-uses sysconst;
+uses
+  {$IFDEF UNIX} Unix, {$ENDIF}
+  {$IFDEF WINDOWS} Windows, {$ENDIF}
+  sysconst;
 {$ENDIF FPC_DOTTEDUNITS}
 {$ENDIF FPC_DOTTEDUNITS}
 
 
 const
 const
   TDateTimeEpsilon = 2.2204460493e-16;
   TDateTimeEpsilon = 2.2204460493e-16;
   HalfMilliSecond = OneMillisecond /2 ;
   HalfMilliSecond = OneMillisecond /2 ;
-  
+  SErrLocalTimeInvalid = 'Invalid local time: %s';
 
 
 { ---------------------------------------------------------------------
 { ---------------------------------------------------------------------
     Auxiliary routines
     Auxiliary routines
@@ -1436,10 +1481,10 @@ begin
 end;
 end;
 
 
 
 
-function TimeInRange(ATime: TTime; AStartTime, AEndTime: TTime; AInclusive: Boolean = True): Boolean;
+function TimeInRange(ATime: System.TTime; AStartTime, AEndTime: System.TTime; AInclusive: Boolean = True): Boolean;
 
 
 var
 var
- LTime, LStartTime, LEndTime: TTime;
+ LTime, LStartTime, LEndTime: System.TTime;
  
  
 begin
 begin
   LTime:=TimeOf(ATime);
   LTime:=TimeOf(ATime);
@@ -3567,8 +3612,234 @@ begin
   Result:= ParseLocal(Date,local);
   Result:= ParseLocal(Date,local);
 end;
 end;
 
 
+type
+  TLocalTimeZone = class(TTimeZone)
+  protected
+    procedure DoGetOffsetsAndType(const aDateTime: TDateTime; out aOffset, aDstSave: Int64; out aType: TLocalTimeType); override;
+    function DoGetDisplayName(const aDateTime: TDateTime; const aForceDaylight: Boolean): string; override;
+    function DoGetID(): string; override;
+  end;
 
 
-{$else}
-implementation
-{$endif FPUNONE}
+{ TTimeZone }
+
+function TTimeZone.ToLocalTime(const aDateTime: TDateTime): TDateTime;
+
+var
+  ltt: TLocalTimeType;
+  lOffset,lDst: Int64;
+
+begin
+  { UTC -> Local }
+  DoGetOffsetsAndType(aDateTime,lOffset,lDst,ltt);
+  Result:=IncSecond(aDateTime,lOffset);
+  DoGetOffsetsAndType(Result,lOffset,lDst,ltt);
+  if (ltt in [lttInvalid,lttDaylight]) then
+    Result:=IncSecond(Result,lDst);
+end;
+
+function TTimeZone.ToUniversalTime(const aDateTime: TDateTime; const aForceDaylight: Boolean): TDateTime;
+var
+  lOffset: Int64;
+begin
+  LOffset:=GetUtcOffsetInSeconds(ADateTime,aForceDaylight);
+  Result:=IncSecond(ADateTime,-lOffset);
+end;
+
+class constructor TTimeZone.Init;
+
+begin
+  _Local:=TLocalTimeZone.Create;
+end;
+
+class destructor TTimeZone.Done;
+
+begin
+  FreeAndNil(_local);
+end;
+
+function TTimeZone.GetCurrentAbbreviation: string;
+
+begin
+  Result:=GetAbbreviation(Now);
+end;
+
+function TTimeZone.GetCurrentDisplayName: string;
+
+begin
+  Result:=GetDisplayName(Now);
+end;
+
+function TTimeZone.GetLocalTimeType(const ADateTime: TDateTime): TLocalTimeType;
+var
+  LOffset, LDSTSave: Int64;
+begin
+  DoGetOffsetsAndType(ADateTime, LOffset, LDSTSave, Result);
+end;
+
+
+function TTimeZone.GetDisplayName(const aDateTime: TDateTime; const aForceDaylight: Boolean): string;
+begin
+  if IsInvalidTime(aDateTime) then
+    raise ELocalTimeInvalid.CreateFmt(SErrLocalTimeInvalid,[DateTimeToStr(aDateTime)]);
+  Result:=DoGetDisplayName(aDateTime,aForceDaylight);
+end;
+
+function TTimeZone.GetAbbreviation(const aDateTime: TDateTime; const aForceDaylight: Boolean): string;
+
+const
+  SignChars : Array[Boolean] of Char = ('-','+');
+
+var
+  lHrs, LMins: Integer;
+  lOffset: Int64;
+  lSign: Char;
+
+begin
+  lOffset:=GetUtcOffsetInSeconds(aDateTime,aForceDaylight);
+  if lOffset=0 then
+    Exit('GMT');
+  // no divmod for int64
+  lHrs:=Abs(lOffset) div SecsPerHour;
+  lMins:=(Abs(lOffset) mod SecsPerHour) div SecsPerMin;
+  lSign:=SignChars[lOffset>0];
+  Result:='GMT'+lSign+Format('%.2d',[lHrs]);
+  Result:=Result+':'+Format('%.2d',[lMins]);
+end;
+
+function TTimeZone.GetUtcOffset(const aDateTime: TDateTime; const aForceDaylight: Boolean): TTimeSpan;
+begin
+  Result:=TTimeSpan.FromSeconds(GetUtcOffsetInSeconds(aDateTime,aForceDaylight));
+end;
+
+function TTimeZone.GetUtcOffsetInSeconds(const aDateTime: TDateTime; const aForceDaylight: Boolean): Int64;
+var
+  lOffset, lDst: Int64;
+  ltt: TLocalTimeType;
+begin
+  DoGetOffsetsAndType(aDateTime,lOffset,lDst,ltt);
+  Result:=lOffset;
+  Case ltt of
+  lttDaylight:
+    Result:=Result+lDst;
+  lttAmbiguous:
+    if aForceDaylight then
+      Result:=Result+lDst;
+  lttInvalid:
+    raise ELocalTimeInvalid.CreateFmt(SErrLocalTimeInvalid,[DateTimeToStr(aDateTime)]);
+  else
+    // nothing to do
+  end;
+end;
+
+function TTimeZone.IsAmbiguousTime(const aDateTime: TDateTime): Boolean;
+begin
+  Result:=(lttAmbiguous=GetLocalTimeType(aDateTime));
+end;
+
+function TTimeZone.IsDaylightTime(const aDateTime: TDateTime; const aForceDaylight: Boolean): Boolean;
+
+var
+  ltt : TLocalTimeType;
+
+begin
+  ltt:=GetLocalTimeType(aDateTime);
+  Result:=(ltt=lttDaylight) or ((ltt=lttAmbiguous) and aForceDaylight);
+end;
+
+function TTimeZone.IsStandardTime(const aDateTime: TDateTime; const aForceDaylight: Boolean): Boolean;
+var
+  ltt: TLocalTimeType;
+begin
+  ltt:=GetLocalTimeType(ADateTime);
+  Result:=(ltt=lttStandard) or ((ltt=lttAmbiguous) and not aForceDaylight);
+end;
+
+function TTimeZone.IsInvalidTime(const aDateTime: TDateTime): Boolean;
+begin
+  Result:=(lttInvalid=GetLocalTimeType(ADateTime));
+end;
+
+function TTimeZone.HasDST(const aYear: TDateTime): Boolean;
+
+var
+  ltt: TLocalTimeType;
+  lOffset,lDst: Int64;
+
+begin
+  DoGetOffsetsAndType(aYear,lOffset,lDst,ltt);
+  Result:=lDst<>0;
+end;
+
+function TTimeZone.HasDST: Boolean;
+begin
+  Result:=HasDST(Now);
+end;
+
+function TTimeZone.GetCurrentUtcOffset: TTimeSpan;
+begin
+  Result:=GetUtcOffset(Now);
+end;
+
+function TLocalTimeZone.DoGetID(): string;
+
+{$IFDEF WINDOWS}
+var
+  lInfo: TTimeZoneInformation;
+  Name : UnicodeString;
+{$ENDIF}
+{$IFDEF UNIX}
+var
+  lInfo : TTZInfoEx;
+{$ENDIF}
+{$IF NOT (DEFINED(WINDOWS) OR DEFINED(UNIX))}
+var
+  lTZEnv : String;
+  I : integer;
+{$ENDIF}
+begin
+  Result:='';
+  {$IFDEF UNIX}
+  lInfo:=GetTZInfoEx;
+  Result:=lInfo.Name[false];
+  {$ENDIF}
+  {$IFDEF WINDOWS}
+  lInfo:=Default(TTimeZoneInformation);
+  GetTimeZoneInformation(lInfo);
+  Name:=StrPas(@lInfo.StandardName);
+  {$IF SIZEOF(CHAR)=1}
+  Result:=UTF8Encode(Name);
+  {$ELSE}
+  Result:=Name;
+  {$ENDIF}
+  {$ENDIF}
+  {$IF NOT (DEFINED(WINDOWS) OR DEFINED(UNIX))}
+  lTZEnv:=GetEnvironmentVariable('TZ');
+  I:=1;
+  While (I<=Length(LTZEnv)) and (UpCase(LTZEnv[i]) in ['A'..'Z']) do
+    Result:=Result+LTZEnv[i];
+  {$ENDIF}
+end;
+
+
+function TLocalTimeZone.DoGetDisplayName(const aDateTime: TDateTime; const aForceDaylight: Boolean): string;
+
+begin
+  Result:=ID;// For the moment, it is not clear what this should do.
+end;
+
+procedure TLocalTimeZone.DoGetOffsetsAndType(const aDateTime: TDateTime;
+  out aOffset, aDstSave: Int64; out aType: TLocalTimeType);
+var
+  lOffset : Integer;
+  lDst : Boolean;
+begin
+  GetLocalTimeOffset(aDateTime,False,lOffset,lDst);
+  aOffSet:=lOffset;
+  if lDST then
+    aType:=lttDaylight
+  else
+    aType:=lttStandard;
+end;
+
+{$endif}
 end.
 end.