git-svn-id: https://192.168.0.254/svn/Componentes.Terceros.jcl@20 c37d764d-f447-7644-a108-883140d013fb
825 lines
30 KiB
ObjectPascal
825 lines
30 KiB
ObjectPascal
{**************************************************************************************************}
|
|
{ }
|
|
{ Project JEDI Code Library (JCL) }
|
|
{ }
|
|
{ The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); }
|
|
{ you may not use this file except in compliance with the License. You may obtain a copy of the }
|
|
{ License at http://www.mozilla.org/MPL/ }
|
|
{ }
|
|
{ Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF }
|
|
{ ANY KIND, either express or implied. See the License for the specific language governing rights }
|
|
{ and limitations under the License. }
|
|
{ }
|
|
{ The Original Code is TimeZones.pas. }
|
|
{ }
|
|
{ The Initial Developer of the Original Code is Rik Barker }
|
|
{ <rik dott barker att visionsoft dott com>. }
|
|
{ Portions created by Rik Barker are Copyright (C) Rik Barker. All Rights Reserved. }
|
|
{ }
|
|
{ Contributors: }
|
|
{ }
|
|
{**************************************************************************************************}
|
|
{ }
|
|
{ Routines and classes for working with Timezones and UTC dates. }
|
|
{ }
|
|
{**************************************************************************************************}
|
|
{ }
|
|
{ Last modified: $Date:: 2009-09-13 11:57:20 +0200 (dim., 13 sept. 2009) $ }
|
|
{ Revision: $Rev:: 3009 $ }
|
|
{ Author: $Author:: outchy $ }
|
|
{ }
|
|
{**************************************************************************************************}
|
|
|
|
|
|
{**************************************************************************************************}
|
|
{ Important: }
|
|
{ Information on Timezones is pretty sparse and often vague. }
|
|
{ According to MSDN GetTimeZoneInformation will return TIME_ZONE_ID_DAYLIGHT if }
|
|
{ }
|
|
{ "the system is operating in the range covered by the DaylightDate }
|
|
{ member of the TIME_ZONE_INFORMATION structure." }
|
|
{ }
|
|
{ What it fails to mention is that it will take liberties with the TIME_ZONE_INFORMATION structure.}
|
|
{ Unless "Automatically Adjust clock for Daylight savings changes" is enabled, DayLightDate }
|
|
{ will contain StandardDate and DayLightName will contain StandardName. }
|
|
{ So you know you're supposed to be in daylight savings, but you can't find out when it started or }
|
|
{ what the Daylight Name is. }
|
|
{ }
|
|
{ Where possible use the functions/classes that read the data direct from the registry, }
|
|
{ these will always return the correct daylight savings date and name }
|
|
{**************************************************************************************************}
|
|
|
|
unit JclTimeZones;
|
|
|
|
{$I jcl.inc}
|
|
{$I windowsonly.inc}
|
|
|
|
interface
|
|
|
|
uses
|
|
{$IFDEF UNITVERSIONING}
|
|
JclUnitVersioning,
|
|
{$ENDIF UNITVERSIONING}
|
|
Windows, SysUtils, Contnrs, Classes;
|
|
|
|
type
|
|
// Contents of the TZI value in the Time Zones section of the registry
|
|
TJclTZIValueInfo = record
|
|
Bias: Longint;
|
|
StandardBias: Integer;
|
|
DaylightBias: Integer;
|
|
StandardDate: TSystemTime;
|
|
DaylightDate: TSystemTime;
|
|
end;
|
|
|
|
// All other Time Zone values in the registry
|
|
PJclTimeZoneRegInfo = ^TJclTimeZoneRegInfo;
|
|
TJclTimeZoneRegInfo = record
|
|
DisplayDesc: string;
|
|
StandardName: string;
|
|
DaylightName: string;
|
|
SortIndex: Integer;
|
|
MapID: string;
|
|
TZI: TJclTZIValueInfo;
|
|
end;
|
|
|
|
type
|
|
// Callback prototype for EnumTimeZones
|
|
TJclTimeZoneCallBackFunc = function(const TimeZoneRec: TJclTimeZoneRegInfo): Boolean of object;
|
|
|
|
type
|
|
TJclTimeZoneInfo = class(TObject)
|
|
private
|
|
FStandardName: string;
|
|
FDaylightName: string;
|
|
FTZDescription: string;
|
|
FSortIndex: Integer; // Order to sort the timezones into
|
|
FMapID: string; // Coordinates for the Date/Time properties map
|
|
FBiasInfo: TJclTZIValueInfo; // Bias information from the registry
|
|
function GetActiveBias: Integer;
|
|
function GetCurrentDateTime: TDateTime;
|
|
function GetDaylightSavingsStartDate: TDateTime;
|
|
function GetGMTOffset: string;
|
|
function GetStandardStartDate: TDateTime;
|
|
function GetSupportsDaylightSavings: Boolean;
|
|
function GetTimeZoneType(TZI: TJclTZIValueInfo): Cardinal;
|
|
public
|
|
procedure Assign(Source: TJclTimeZoneRegInfo);
|
|
procedure ApplyTimeZone;
|
|
function DayLightSavingsPeriod: string;
|
|
function DateTimeIsInDaylightSavings(ADateTime: TDateTime):Boolean;
|
|
function StandardStartDateInYear(const AYear: Integer): TDateTime;
|
|
function DaylightStartDateInYear(const AYear: Integer): TDateTime;
|
|
// These are all the values we want to be able to stream
|
|
property ActiveBias: Integer read GetActiveBias;
|
|
property CurrentDateTime: TDateTime read GetCurrentDateTime;
|
|
property DaylightName: string read FDaylightName;
|
|
property DaylightSavingsStartDate: TDateTime read GetDaylightSavingsStartDate;
|
|
property DisplayDescription: string read FTZDescription;
|
|
property GMTOffset: string read GetGMTOffset;
|
|
property MapID: string read FMapID;
|
|
property SortIndex: Integer read FSortIndex;
|
|
property StandardName: string read FStandardName;
|
|
property StandardStartDate: TDateTime read GetStandardStartDate;
|
|
property SupportsDaylightSavings: Boolean read GetSupportsDaylightSavings;
|
|
end;
|
|
|
|
TJclTimeZones = class(TObject)
|
|
private
|
|
FActiveTimeZoneIndex: Integer;
|
|
FTimeZones: TObjectList;
|
|
FAutoAdjustEnabled: Boolean;
|
|
function GetAutoAdjustEnabled: Boolean;
|
|
function GetActiveTimeZoneInfo: TJclTimeZoneInfo;
|
|
function GetCount: Integer;
|
|
function GetItem(Index: Integer): TJclTimeZoneInfo;
|
|
procedure LoadTimeZones;
|
|
function TimeZoneCallback(const TimeZoneRec: TJclTimeZoneRegInfo): Boolean;
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
function SetDateTime(DateTime: TDateTime): Boolean;
|
|
procedure SetAutoAdjustEnabled(Value: Boolean);
|
|
property Count: Integer read GetCount;
|
|
property Items[Index: Integer]: TJclTimeZoneInfo read GetItem; default;
|
|
property ActiveTimeZone: TJclTimeZoneInfo read GetActiveTimeZoneInfo;
|
|
property AutoAdjustEnabled: Boolean read FAutoAdjustEnabled write FAutoAdjustEnabled;
|
|
end;
|
|
|
|
type
|
|
EDaylightSavingsNotSupported = class(Exception);
|
|
EAutoAdjustNotEnabled = class(Exception);
|
|
|
|
// Enumerate all time zones from the registry
|
|
function EnumTimeZones(CallBackFunc: TJclTimeZoneCallBackFunc): Boolean;
|
|
|
|
// Functions that read from the current time zone
|
|
function IsAutoAdjustEnabled: Boolean;
|
|
function CurrentTimeZoneSupportsDaylightSavings: Boolean;
|
|
function DateCurrentTimeZoneClocksChangeToStandard: TDateTime;
|
|
function DateCurrentTimeZoneClocksChangeToDaylightSavings: TDateTime;
|
|
function GetCurrentTimeZoneDescription: string;
|
|
function GetCurrentTimeZoneDaylightSavingsPeriod: string;
|
|
function GetCurrentTimeZoneGMTOffset: string;
|
|
function GetCurrentTimeZoneUTCBias: Integer;
|
|
|
|
// Misc UTC related functions
|
|
function GetWMIScheduledJobUTCTime(Time: TDateTime): string;
|
|
function UTCNow: TDateTime;
|
|
|
|
{$IFDEF UNITVERSIONING}
|
|
const
|
|
UnitVersioning: TUnitVersionInfo = (
|
|
RCSfile: '$URL: https://jcl.svn.sourceforge.net/svnroot/jcl/tags/JCL-2.1-Build3536/jcl/source/windows/JclTimeZones.pas $';
|
|
Revision: '$Revision: 3009 $';
|
|
Date: '$Date: 2009-09-13 11:57:20 +0200 (dim., 13 sept. 2009) $';
|
|
LogPath: 'JCL\source\windows';
|
|
Extra: '';
|
|
Data: nil
|
|
);
|
|
{$ENDIF UNITVERSIONING}
|
|
|
|
implementation
|
|
|
|
uses
|
|
Messages,
|
|
JclResources,
|
|
JclDateTime, JclRegistry;
|
|
|
|
const
|
|
cAutoAdjustKey = '\System\CurrentControlSet\Control\TimeZoneInformation';
|
|
cAutoAdjustValue = 'DisableAutoDaylightTimeSet';
|
|
|
|
const
|
|
UTCDays: array [0..6] of PChar =
|
|
('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday');
|
|
UTCDaysOffset: array [1..5] of PChar =
|
|
('first', 'second', 'third', 'fourth', 'last');
|
|
UTCMonths: array [1..12] of string =
|
|
('January', 'February', 'March', 'April', 'May', 'June',
|
|
'July', 'August', 'September', 'October', 'November', 'December');
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Callback enumerator of timezone information. Return false to stop enumeration
|
|
|
|
function EnumTimeZones(CallBackFunc: TJclTimeZoneCallBackFunc): Boolean;
|
|
var
|
|
TimeZoneRootName: string;
|
|
TimeZones: TStringList;
|
|
I: Integer;
|
|
TimeZoneRegInfo: TJclTimeZoneRegInfo;
|
|
begin
|
|
Result := True;
|
|
|
|
if not Assigned(CallBackFunc) then
|
|
raise Exception.Create(RsENoCallbackFunc);
|
|
|
|
TimeZones := TStringList.Create;
|
|
try
|
|
if Win32Platform = VER_PLATFORM_WIN32_WINDOWS then
|
|
TimeZoneRootName := '\Software\Microsoft\Windows\CurrentVersion\Time Zones\'
|
|
else
|
|
TimeZoneRootName := '\Software\Microsoft\Windows NT\CurrentVersion\Time Zones\';
|
|
|
|
if not RegGetKeyNames(HKEY_LOCAL_MACHINE, TimeZoneRootName, TimeZones) then
|
|
Exit;
|
|
|
|
for I := 0 to TimeZones.Count - 1 do
|
|
begin
|
|
FillChar(TimeZoneRegInfo, SizeOf(TJclTimeZoneRegInfo), #0);
|
|
|
|
TimeZoneRegInfo.DisplayDesc := RegReadString(HKEY_LOCAL_MACHINE, TimeZoneRootName + TimeZones[I], 'Display');
|
|
TimeZoneRegInfo.StandardName := RegReadString(HKEY_LOCAL_MACHINE, TimeZoneRootName + TimeZones[I], 'Std');
|
|
TimeZoneRegInfo.DaylightName := RegReadString(HKEY_LOCAL_MACHINE, TimeZoneRootName + TimeZones[I], 'Dlt');
|
|
TimeZoneRegInfo.SortIndex := RegReadIntegerDef(HKEY_LOCAL_MACHINE, TimeZoneRootName + TimeZones[I], 'Index', -1);
|
|
TimeZoneRegInfo.MapID := RegReadStringDef(HKEY_LOCAL_MACHINE, TimeZoneRootName + TimeZones[I], 'MapID', '');
|
|
RegReadBinary(HKEY_LOCAL_MACHINE, TimeZoneRootName + TimeZones[I], 'TZI', TimeZoneRegInfo.TZI,
|
|
SizeOf(TJclTZIValueInfo));
|
|
|
|
// Allow the callback function to stop the enumeration
|
|
if not CallBackFunc(TimeZoneRegInfo) then
|
|
Break;
|
|
end;
|
|
finally
|
|
TimeZones.Free;
|
|
end;
|
|
end;
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Translates dates like "the third thursday in March" into the actual date
|
|
|
|
function CalculateTransitionDate(TransitionInfo: TSystemTime; CalculateForYear: Integer= -1): TDateTime;
|
|
var
|
|
CheckDate, FirstOfMonth: TDateTime;
|
|
MonthStartDOW: Integer;
|
|
TransitionDOW: Word;
|
|
TransitionDay: Word;
|
|
PartialDays: Integer;
|
|
I, MonthDays: Integer;
|
|
Year, Month, Day: Word;
|
|
ActiveYear: Integer;
|
|
begin
|
|
if CalculateForYear= -1 then
|
|
ActiveYear:=YearOfDate(Now)
|
|
else
|
|
ActiveYear:=CalculateForYear;
|
|
|
|
// If the date is empty, we've been passed a system date for a timezone that has no daylight support
|
|
if (TransitionInfo.wYear = 0) and (TransitionInfo.wMonth = 0) and (TransitionInfo.wDay = 0) then
|
|
begin
|
|
Result := 0;
|
|
Exit;
|
|
end;
|
|
|
|
// Work out what day the first of the month is
|
|
FirstOfMonth := EncodeDate(ActiveYear, TransitionInfo.wMonth, 1);
|
|
|
|
// System time is 0 based, Day of Week is 1 based
|
|
MonthStartDOW := DayOfWeek(FirstOfMonth);
|
|
TransitionDOW := TransitionInfo.wDayOfWeek + 1;
|
|
|
|
if MonthStartDOW > TransitionDOW then
|
|
PartialDays := 7 - (MonthStartDOW - TransitionDOW) + 1
|
|
else
|
|
PartialDays := (TransitionDOW - MonthStartDOW) + 1;
|
|
|
|
// According to MSDN, wDay member is a value in the range 1 through 5.
|
|
// Using this notation, the first Sunday in April can be specified,
|
|
// as can the last Thursday in October (5 is equal to "the last").
|
|
TransitionDay := 1;
|
|
case TransitionInfo.wDay of
|
|
1:
|
|
TransitionDay := PartialDays;
|
|
2, 3, 4:
|
|
TransitionDay := (7 * (TransitionInfo.wDay - 1)) + PartialDays;
|
|
5:
|
|
begin
|
|
// Work out the date of the last X day in the month
|
|
MonthDays := DaysInMonth(FirstOfMonth);
|
|
DecodeDate(FirstOfMonth, Year, Month, Day);
|
|
|
|
for I := MonthDays downto MonthDays - 7 do
|
|
begin
|
|
CheckDate := EncodeDate(Year, Month, I);
|
|
if DayOfWeek(CheckDate) = TransitionDOW then
|
|
begin
|
|
TransitionDay := I;
|
|
Break;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
Result := EncodeDate(ActiveYear, TransitionInfo.wMonth, TransitionDay);
|
|
end;
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
//Returns the date range in friendly format "From the 1st sunday in March to the last Sunday in October"
|
|
|
|
function GetDayLightSavingsPeriod(StandardDate, DayLightDate: TSystemTime): string;
|
|
begin
|
|
if (DaylightDate.wMonth = 0) and (StandardDate.wMonth = 0) then
|
|
Result := '' // There is no daylight savings period for this timezone
|
|
else
|
|
begin
|
|
if (DaylightDate.wMonth = StandardDate.wMonth) and (DaylightDate.wDay = StandardDate.wDay) then
|
|
Result := '' // AutoAdjust for Daylight savings is not enabled, so Windows returns the entire year
|
|
else
|
|
begin
|
|
Result := 'From the ' + UTCDaysOffset[DaylightDate.wDay] + ' ' + UTCDays[DaylightDate.wDayOfWeek] + ' of ' +
|
|
UTCMonths[DaylightDate.wMonth] +
|
|
' to the ' + UTCDaysOffset[StandardDate.wDay] + ' ' + UTCDays[StandardDate.wDayOfWeek] + ' of ' +
|
|
UTCMonths[StandardDate.wMonth];
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Functions relating to the current time zone
|
|
//--------------------------------------------------------------------------------------------------
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Returns true if the current timezone supports daylight savings - regardless of whether
|
|
// AutoAdjust is enabled or not
|
|
|
|
function CurrentTimeZoneSupportsDaylightSavings: Boolean;
|
|
var
|
|
TimeZoneInfo: TTimeZoneInformation;
|
|
begin
|
|
Result := False;
|
|
|
|
FillChar(TimeZoneInfo, SizeOf(TimeZoneInfo), #0);
|
|
case GetTimeZoneInformation(TimeZoneInfo) of
|
|
TIME_ZONE_ID_STANDARD:
|
|
Result := TimeZoneInfo.StandardDate.wMonth <> 0;
|
|
TIME_ZONE_ID_DAYLIGHT:
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Returns the exact date that clocks switch to daylight savings in the current
|
|
// year for the current time zone
|
|
|
|
function DateCurrentTimeZoneClocksChangeToDaylightSavings: TDateTime;
|
|
var
|
|
TimeZoneInfo: TTimeZoneInformation;
|
|
begin
|
|
// The daylight savings time is only returned if AutoAdjust is enabled and the
|
|
// timezone supports Daylight savings.
|
|
if not CurrentTimeZoneSupportsDaylightSavings then
|
|
raise EDaylightSavingsNotSupported.Create(RsEDaylightSavingsNotSupported);
|
|
|
|
if not IsAutoAdjustEnabled then
|
|
raise EAutoAdjustNotEnabled.Create(RsEAutoAdjustNotEnabled);
|
|
|
|
Result := 0;
|
|
FillChar(TimeZoneInfo, SizeOf(TimeZoneInfo), #0);
|
|
if GetTimeZoneInformation(TimeZoneInfo) in [TIME_ZONE_ID_DAYLIGHT, TIME_ZONE_ID_STANDARD] then
|
|
Result := CalculateTransitionDate(TimeZoneInfo.DaylightDate);
|
|
end;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Returns the exact date that clocks switch to standard time in the current year
|
|
// for the current time zone
|
|
|
|
function DateCurrentTimeZoneClocksChangeToStandard: TDateTime;
|
|
var
|
|
TimeZoneInfo: TTimeZoneInformation;
|
|
begin
|
|
if not CurrentTimeZoneSupportsDaylightSavings then
|
|
raise EDaylightSavingsNotSupported.Create(RsEDaylightSavingsNotSupported);
|
|
|
|
Result := 0;
|
|
FillChar(TimeZoneInfo, SizeOf(TimeZoneInfo), #0);
|
|
if GetTimeZoneInformation(TimeZoneInfo) in [TIME_ZONE_ID_DAYLIGHT, TIME_ZONE_ID_STANDARD] then
|
|
Result := CalculateTransitionDate(TimeZoneInfo.StandardDate);
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Returns the name of the current time zone
|
|
|
|
function GetCurrentTimeZoneDescription: string;
|
|
var
|
|
TimeZoneInfo: TTimeZoneInformation;
|
|
begin
|
|
FillChar(TimeZoneInfo, SizeOf(TimeZoneInfo), #0);
|
|
case GetTimeZoneInformation(TimeZoneInfo) of
|
|
TIME_ZONE_ID_INVALID:
|
|
Result := '';
|
|
TIME_ZONE_ID_STANDARD:
|
|
Result := TimeZoneInfo.StandardName;
|
|
TIME_ZONE_ID_UNKNOWN:
|
|
Result := TimeZoneInfo.StandardName;
|
|
TIME_ZONE_ID_DAYLIGHT:
|
|
Result := TimeZoneInfo.DaylightName;
|
|
end;
|
|
end;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Returns the date range in friendly format for the current timezone
|
|
|
|
function GetCurrentTimeZoneDaylightSavingsPeriod: string;
|
|
var
|
|
TimeZoneInfo: TTimeZoneInformation;
|
|
begin
|
|
Result := '';
|
|
if not IsAutoAdjustEnabled then
|
|
Exit;
|
|
|
|
FillChar(TimeZoneInfo, SizeOf(TimeZoneInfo), #0);
|
|
if GetTimeZoneInformation(TimeZoneInfo) = TIME_ZONE_ID_INVALID then
|
|
Exit;
|
|
|
|
Result := GetDaylightSavingsPeriod(TimeZoneInfo.StandardDate, TimeZoneInfo.DaylightDate);
|
|
end;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Returns the current offset from GMT as a string "GMT+03:00"
|
|
|
|
function GetCurrentTimeZoneGMTOffset: string;
|
|
var
|
|
Bias: Integer;
|
|
Hours, Minutes: Integer;
|
|
begin
|
|
Bias := GetCurrentTimeZoneUTCBias;
|
|
|
|
Hours := Bias div 60;
|
|
Minutes := Abs(Bias) mod 60;
|
|
if Bias >= 0 then
|
|
Result := Format('GMT+%.2d:%.2d', [Hours, Minutes])
|
|
else
|
|
// (rom) not GMT- here?
|
|
Result := Format('GMT%.2d:%.2d', [Hours, Minutes]);
|
|
end;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Returns the Current Time Zone UTC Bias from GMT in minutes
|
|
|
|
function GetCurrentTimeZoneUTCBias: Integer;
|
|
var
|
|
TimeZoneInfo: TTimeZoneInformation;
|
|
begin
|
|
Result := 0;
|
|
FillChar(TimeZoneInfo, SizeOf(TimeZoneInfo), #0);
|
|
case GetTimeZoneInformation(TimeZoneInfo) of
|
|
TIME_ZONE_ID_STANDARD:
|
|
Result := -(TimeZoneInfo.Bias + TimeZoneInfo.StandardBias);
|
|
TIME_ZONE_ID_UNKNOWN:
|
|
Result := -(TimeZoneInfo.Bias + TimeZoneInfo.StandardBias);
|
|
TIME_ZONE_ID_DAYLIGHT:
|
|
Result := -(TimeZoneInfo.Bias + TimeZoneInfo.DaylightBias);
|
|
end;
|
|
end;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Miscellaneous UTC functions
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Returns the current UTC time
|
|
|
|
function UTCNow: TDateTime;
|
|
var
|
|
SystemTime: TSystemTime;
|
|
begin
|
|
GetSystemTime(SystemTime);
|
|
Result := EncodeDate(SystemTime.wYear, SystemTime.wMonth, SystemTime.wDay) +
|
|
EncodeTime(SystemTime.wHour, SystemTime.wMinute, SystemTime.wSecond, SystemTime.wMilliseconds);
|
|
end;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Returns Time in the UTC string format expected by root\cimv2\Win32_ScheduledJob
|
|
// TODO: This works for scheduling local jobs, but fails on remote jobs - looks
|
|
// like Windows does something a bit odd with remote scheduled jobs, it's not
|
|
// handling the time correctly at all. So much for UTC.
|
|
|
|
function GetWMIScheduledJobUTCTime(Time: TDateTime): string;
|
|
var
|
|
TimeBias: Integer;
|
|
Hour, Min, Sec, MSec: Word;
|
|
begin
|
|
Result := '';
|
|
|
|
TimeBias := GetCurrentTimeZoneUTCBias;
|
|
|
|
DecodeTime(Time, Hour, Min, Sec, MSec);
|
|
|
|
Result := '********' + Format('%.2d%.2d%.2d.000000', [Hour, Min, Sec]);
|
|
|
|
if TimeBias >= 0 then
|
|
Result := Result + Format('+%.3d', [TimeBias])
|
|
else
|
|
Result := Result + Format('%.3d', [TimeBias]);
|
|
end;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Returns true if "Automatically Adjust clock for daylight saving changes" is checked
|
|
|
|
function IsAutoAdjustEnabled: Boolean;
|
|
begin
|
|
Result := RegReadIntegerDef(HKEY_LOCAL_MACHINE, cAutoAdjustKey, cAutoAdjustValue, 0) = 0;
|
|
end;
|
|
|
|
//=== { TJclTimeZoneInfo } ====================================================
|
|
|
|
procedure TJclTimeZoneInfo.ApplyTimeZone;
|
|
var
|
|
TimeZoneInfo: TTimeZoneInformation;
|
|
begin
|
|
TimeZoneInfo.Bias := FBiasInfo.Bias;
|
|
|
|
StringToWideChar(FStandardName, TimeZoneInfo.StandardName, 32);
|
|
TimeZoneInfo.StandardDate := FBiasInfo.StandardDate;
|
|
TimeZoneInfo.StandardBias := FBiasInfo.StandardBias;
|
|
StringToWideChar(FDaylightName, TimeZoneInfo.DaylightName, 32);
|
|
TimeZoneInfo.DaylightDate := FBiasInfo.DaylightDate;
|
|
TimeZoneInfo.DaylightBias := FBiasInfo.DaylightBias;
|
|
|
|
if not SetTimeZoneInformation({$IFDEF FPC}@{$ENDIF FPC}TimeZoneInfo) then
|
|
RaiseLastOSError;
|
|
|
|
SendMessage(HWND_BROADCAST, WM_SETTINGCHANGE, 0, 0);
|
|
end;
|
|
|
|
procedure TJclTimeZoneInfo.Assign(Source: TJclTimeZoneRegInfo);
|
|
begin
|
|
FStandardName := Source.StandardName;
|
|
FDaylightName := Source.DaylightName;
|
|
FTZDescription := Source.DisplayDesc;
|
|
FSortIndex := Source.SortIndex;
|
|
FMapID := Source.MapID;
|
|
FBiasInfo := Source.TZI;
|
|
end;
|
|
|
|
function TJclTimeZoneInfo.GetActiveBias: Integer;
|
|
begin
|
|
// Return the active bias (including any skew derived from auto-adjust being enabled)
|
|
Result := 0;
|
|
|
|
case GetTimeZoneType(FBiasInfo) of
|
|
TIME_ZONE_ID_STANDARD:
|
|
Result := -(FBiasInfo.Bias + FBiasInfo.StandardBias);
|
|
TIME_ZONE_ID_UNKNOWN:
|
|
Result := -(FBiasInfo.Bias + FBiasInfo.StandardBias);
|
|
TIME_ZONE_ID_DAYLIGHT:
|
|
// the bias is different if autoadjust is turned off
|
|
if IsAutoAdjustEnabled then
|
|
Result := -(FBiasInfo.Bias + FBiasInfo.DaylightBias)
|
|
else
|
|
Result := -(FBiasInfo.Bias + FBiasInfo.StandardBias);
|
|
end;
|
|
end;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Returns the exact date that clocks switch to daylight savings in the current
|
|
// year for the specified standard date
|
|
|
|
function TJclTimeZoneInfo.DayLightSavingsPeriod: string;
|
|
begin
|
|
Result := GetDayLightSavingsPeriod(FBiasInfo.StandardDate, FBiasInfo.DayLightDate);
|
|
end;
|
|
|
|
function TJclTimeZoneInfo.GetDaylightSavingsStartDate: TDateTime;
|
|
begin
|
|
Result := CalculateTransitionDate(FBiasInfo.DaylightDate);
|
|
end;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Returns the offset from GMT as a string "GMT+03:00"
|
|
|
|
function TJclTimeZoneInfo.GetGMTOffset: string;
|
|
var
|
|
Hours, Minutes: Integer;
|
|
begin
|
|
Hours := -FBiasInfo.Bias div 60;
|
|
Minutes := Abs(FBiasInfo.Bias) mod 60;
|
|
if Hours >= 0 then
|
|
Result := Format('GMT+%.2d:%.2d', [Hours, Minutes])
|
|
else
|
|
// (rom) No GMT- here?
|
|
Result := Format('GMT%.2d:%.2d', [Hours, Minutes]);
|
|
end;
|
|
|
|
function TJclTimeZoneInfo.GetStandardStartDate: TDateTime;
|
|
begin
|
|
Result := CalculateTransitionDate(FBiasInfo.StandardDate);
|
|
end;
|
|
|
|
function TJclTimeZoneInfo.GetSupportsDaylightSavings: Boolean;
|
|
begin
|
|
Result := False;
|
|
|
|
// TODO: Check this is correct on 9x
|
|
case GetTimeZoneType(FBiasInfo) of
|
|
TIME_ZONE_ID_STANDARD:
|
|
Result := FBiasInfo.StandardDate.wMonth <> 0;
|
|
TIME_ZONE_ID_DAYLIGHT:
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
function TJclTimeZoneInfo.GetCurrentDateTime: TDateTime;
|
|
begin
|
|
// Return the current date time in this time zone
|
|
Result := UTCNow + (GetActiveBias / 1440);
|
|
end;
|
|
|
|
// Returns the TimeZone type based on StandardDate and DaylightDate
|
|
|
|
function TJclTimeZoneInfo.GetTimeZoneType(TZI: TJclTZIValueInfo): Cardinal;
|
|
var
|
|
StandardDate: TDateTime;
|
|
DaylightSavingsDate: TDateTime;
|
|
begin
|
|
StandardDate := CalculateTransitionDate(TZI.StandardDate);
|
|
DaylightSavingsDate := CalculateTransitionDate(TZI.DaylightDate);
|
|
|
|
if (StandardDate = 0) and (DayLightSavingsDate = 0) then
|
|
Result := TIME_ZONE_ID_UNKNOWN
|
|
else
|
|
begin
|
|
// In places like Australia, Daylight savings is after Standard, in the UK it's the other way round
|
|
if DayLightSavingsDate > StandardDate then
|
|
begin
|
|
if (Now >= StandardDate) and (Now < DaylightSavingsDate) then
|
|
Result := TIME_ZONE_ID_STANDARD
|
|
else
|
|
Result := TIME_ZONE_ID_DAYLIGHT;
|
|
end
|
|
else
|
|
begin
|
|
if (Now >= DaylightSavingsDate) and (Now < StandardDate) then
|
|
Result := TIME_ZONE_ID_DAYLIGHT
|
|
else
|
|
Result := TIME_ZONE_ID_STANDARD;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
//=== { TJclTimeZones } =======================================================
|
|
|
|
function TimeZoneSort(Item1, Item2: Pointer): Integer;
|
|
begin
|
|
Result := TJclTimeZoneInfo(Item1).SortIndex - TJclTimeZoneInfo(Item2).SortIndex;
|
|
if (Result = 0) or (TJclTimeZoneInfo(Item1).SortIndex = -1) or (TJclTimeZoneInfo(Item2).SortIndex = -1) then
|
|
Result := CompareText(TJclTimeZoneInfo(Item1).DisplayDescription, TJclTimeZoneInfo(Item2).DisplayDescription);
|
|
end;
|
|
|
|
constructor TJclTimeZones.Create;
|
|
begin
|
|
inherited Create;
|
|
FAutoAdjustEnabled := GetAutoAdjustEnabled;
|
|
FTimeZones := TObjectList.Create(True);
|
|
LoadTimeZones;
|
|
end;
|
|
|
|
destructor TJclTimeZones.Destroy;
|
|
begin
|
|
FreeAndNil(FTimeZones);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TJclTimeZones.GetAutoAdjustEnabled: Boolean;
|
|
begin
|
|
Result := IsAutoAdjustEnabled;
|
|
end;
|
|
|
|
function TJclTimeZones.GetActiveTimeZoneInfo: TJclTimeZoneInfo;
|
|
begin
|
|
Result := GetItem(FActiveTimeZoneIndex);
|
|
end;
|
|
|
|
function TJclTimeZones.GetCount: Integer;
|
|
begin
|
|
Result := FTimeZones.Count;
|
|
end;
|
|
|
|
function TJclTimeZones.GetItem(Index: Integer): TJclTimeZoneInfo;
|
|
begin
|
|
if (Index >= 0) or (Index < FTimeZones.Count) then
|
|
Result := TJclTimeZoneInfo(FTimeZones[Index])
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
procedure TJclTimeZones.LoadTimeZones;
|
|
var
|
|
CurrentTimeZoneDesc: string;
|
|
I: Integer;
|
|
begin
|
|
EnumTimeZones(TimeZoneCallback);
|
|
FTimeZones.Sort(@TimeZoneSort);
|
|
|
|
CurrentTimeZoneDesc := GetCurrentTimeZoneDescription;
|
|
|
|
FActiveTimeZoneIndex := -1;
|
|
for I := 0 to FTimeZones.Count - 1 do
|
|
if (TJclTimeZoneInfo(FTimeZones[I]).StandardName = CurrentTimeZoneDesc) or
|
|
(TJclTimeZoneInfo(FTimeZones[I]).DayLightName = CurrentTimeZoneDesc) then
|
|
begin
|
|
FActiveTimeZoneIndex := I;
|
|
Break;
|
|
end;
|
|
end;
|
|
|
|
procedure TJclTimeZones.SetAutoAdjustEnabled(Value: Boolean);
|
|
begin
|
|
// TODO: PC isn't being notified correctly of the change
|
|
if FAutoAdjustEnabled <> Value then
|
|
begin
|
|
if not Value then
|
|
RegWriteInteger(HKEY_LOCAL_MACHINE, cAutoAdjustKey, cAutoAdjustValue, 1)
|
|
else
|
|
RegDeleteEntry(HKEY_LOCAL_MACHINE, cAutoAdjustKey, cAutoAdjustValue);
|
|
|
|
FAutoAdjustEnabled := Value;
|
|
SendMessage(HWND_BROADCAST, WM_SETTINGCHANGE, 0, LPARAM(PChar('intl')));
|
|
SendMessage(HWND_TOPMOST, WM_TIMECHANGE, 0, 0);
|
|
end;
|
|
end;
|
|
|
|
function TJclTimeZones.TimeZoneCallback(const TimeZoneRec: TJclTimeZoneRegInfo): Boolean;
|
|
var
|
|
TimeZone: TJclTimeZoneInfo;
|
|
begin
|
|
Result := True;
|
|
TimeZone := TJclTimeZoneInfo.Create;
|
|
TimeZone.Assign(TimeZoneRec);
|
|
FTimeZones.Add(TimeZone)
|
|
end;
|
|
|
|
function TJclTimeZones.SetDateTime(DateTime: TDateTime): Boolean;
|
|
var
|
|
SystemTime: TSystemTime;
|
|
begin
|
|
// The date time we have is local. Convert it to UTC
|
|
DateTime := LocalDateTimeToDateTime(DateTime);
|
|
|
|
DecodeDate(DateTime, SystemTime.wYear, SystemTime.wMonth, SystemTime.wDay);
|
|
DecodeTime(DateTime, SystemTime.wHour, SystemTime.wMinute, SystemTime.wSecond, SystemTime.wMilliSeconds);
|
|
|
|
Result := SetSystemTime(SystemTime);
|
|
|
|
if not Result then
|
|
RaiseLastOSError;
|
|
|
|
SendMessage(HWND_TOPMOST, WM_TIMECHANGE, 0, 0);
|
|
end;
|
|
|
|
function TJclTimeZoneInfo.DateTimeIsInDaylightSavings(ADateTime: TDateTime): Boolean;
|
|
var
|
|
dsStartDate: TDateTime;
|
|
stdStartDate: TDateTime;
|
|
Year: Integer;
|
|
begin
|
|
//Return whether the specified date time is DaylightSavings or not
|
|
Result:=False;
|
|
|
|
if not SupportsDaylightSavings then
|
|
Exit;
|
|
|
|
Year:=YearOfDate(ADateTime);
|
|
dsStartDate := CalculateTransitionDate(FBiasInfo.DaylightDate, Year);
|
|
stdStartDate:= CalculateTransitionDate(FBiasInfo.StandardDate, Year);
|
|
|
|
// In places like Australia, Daylight savings is after Standard, in the UK it's the other way round
|
|
if dsStartDate > stdStartDate then
|
|
begin
|
|
if (ADateTime >= stdStartDate) and (ADateTime < dsStartDate) then
|
|
Result := False
|
|
else
|
|
Result := True;
|
|
end
|
|
else
|
|
begin
|
|
if (ADateTime >= dsStartDate) and (ADateTime < stdStartDate) then
|
|
Result := True
|
|
else
|
|
Result := False;
|
|
end;
|
|
end;
|
|
|
|
function TJclTimeZoneInfo.DaylightStartDateInYear(
|
|
const AYear: Integer): TDateTime;
|
|
begin
|
|
Result:= CalculateTransitionDate(FBiasInfo.DaylightDate, AYear);
|
|
end;
|
|
|
|
function TJclTimeZoneInfo.StandardStartDateInYear(
|
|
const AYear: Integer): TDateTime;
|
|
begin
|
|
Result:= CalculateTransitionDate(FBiasInfo.StandardDate, AYear);
|
|
end;
|
|
|
|
{$IFDEF UNITVERSIONING}
|
|
initialization
|
|
RegisterUnitVersion(HInstance, UnitVersioning);
|
|
|
|
finalization
|
|
UnregisterUnitVersion(HInstance);
|
|
{$ENDIF UNITVERSIONING}
|
|
|
|
end.
|
|
|