function TLocalTimeZone.GetChangesForYear(
const AYear: Word): PYearlyChanges;
{$IF defined(MSWINDOWS)}
function GetAbsoluteDateFromRule(
const AYear, AMonth, ADoW, ADoWIndex: Word; AToD: TTime): TDateTime;
const
CReDoW:
array [0 .. 6]
of Integer = (7, 1, 2, 3, 4, 5, 6);
var
LExpDayOfWeek, LActualDayOfWeek: Word;
begin
{ No actual rule }
if (AMonth = 0)
and (ADoW = 0)
and (ADoWIndex = 0)
then
Exit(0);
{ Transform into ISO 8601 day of week }
LExpDayOfWeek := CReDoW[ADoW];
{ Generate a date in the form of: Year/Month/1st of month }
Result := EncodeDate(AYear, AMonth, 1);
{ Get the day of week for this newly crafted date }
LActualDayOfWeek := DayOfTheWeek(Result);
{ We're too far off now, let's decrease the number of days till we get to the desired one }
if LActualDayOfWeek > LExpDayOfWeek
then
Result := IncDay(Result, DaysPerWeek - LActualDayOfWeek + LExpDayOfWeek)
else if (LActualDayOfWeek < LExpDayOfWeek)
Then
Result := IncDay(Result, LExpDayOfWeek - LActualDayOfWeek);
{ Skip the required number of weeks }
Result := IncDay(Result, DaysPerWeek * (ADoWIndex - 1));
{ If we've skipped the day in this moth, go back a few weeks until we get it right again }
while (MonthOf(Result) > AMonth)
do
Result := IncDay(Result, -DaysPerWeek);
{ Add the time part }
Result := Result + AToD;
end;
var
LTZ: TTimeZoneInformation;
LResult: Cardinal;
begin
Result := AllocMem(SizeOf(TYearlyChanges));
LTZ :=
Default(TTimeZoneInformation);
{ Win32 can't handle dates outside this range }
if (AYear <= 1601)
or (AYear > 30827)
then
exit;
{ Use either Vista of lower APIs. If Vista one fails, also use the lower API. }
if TOSVersion.Check(6, 1, 1)
and GetTimeZoneInformationForYear(AYear,
nil, LTZ)
then
LResult := TIME_ZONE_ID_STANDARD
else
LResult := GetTimeZoneInformation(LTZ);
{ Try with the old API }
{ Exit on error }
if LResult = TIME_ZONE_ID_INVALID
then
exit;
{ Try to obtain the daylight adjustment for the specified year }
if LResult <> TIME_ZONE_ID_UNKNOWN
then
begin
with LTZ.StandardDate
do
Result.FEndOfDST := GetAbsoluteDateFromRule(AYear, wMonth, wDayOfWeek,
wDay, EncodeTime(wHour, wMinute, wSecond, 0));
with LTZ.DaylightDate
do
Result.FStartOfDST := GetAbsoluteDateFromRule(AYear, wMonth, wDayOfWeek,
wDay, EncodeTime(wHour, wMinute, wSecond, 0));
end;
Result.FBias := -SecsPerMin * (LTZ.StandardBias + LTZ.Bias);
Result.FBiasWithDST := -SecsPerMin * (LTZ.DaylightBias + LTZ.Bias);
Result.FName := LTZ.StandardName;
Result.FDSTName := LTZ.DaylightName;
end;
{$ELSEIF defined(MACOS)}
function MacToDateTime(
const AbsTime: CFAbsoluteTime;
const ATZ: CFTimeZoneRef): TDateTime;
var
LDate: CFGregorianDate;
begin
{ Decompose the object }
LDate := CFAbsoluteTimeGetGregorianDate(AbsTime, ATZ);
{ Generate a TDateTime now }
with LDate
do
Result := EncodeDateTime(year, month, day, hour, minute, Round(second), 0);
end;
procedure CopyInfo(
const ATZ: CFTimeZoneRef;
const AStartBT: CFAbsoluteTime;
out LPt1DT, LPt2DT: TDateTime;
out LPt1Off, LPt2Off: Int64);
var
L1stTrans, L2ndTrans: CFAbsoluteTime;
begin
{ Calculate the first and the last transition times }
L1stTrans := CFTimeZoneGetNextDaylightSavingTimeTransition(ATZ, AStartBT);
L2ndTrans := CFTimeZoneGetNextDaylightSavingTimeTransition(ATZ, L1stTrans);
{ Obtain the GMT offset before first transition }
LPt1Off := Round(CFTimeZoneGetSecondsFromGMT(ATZ, AStartBT));
if (L1stTrans <> 0)
or (L2ndTrans > L1stTrans)
then
begin
{ Convert the first transition to TDateTime }
LPt1DT := MacToDateTime(L1stTrans, ATZ);
{ Convert the second transition to TDateTime }
LPt2DT := MacToDateTime(L2ndTrans, ATZ);
{ Obtain the GMT offset before second transition }
LPt2Off := Round(CFTimeZoneGetSecondsFromGMT(ATZ, L1stTrans));
end else
begin
{ There were no transitions. Use the base data }
LPt2Off := LPt1Off;
LPt1DT := MacToDateTime(AStartBT, ATZ);
LPt2DT := LPt1DT;
end;
end;
var
LLocaleRef: CFLocaleRef;
L1stJan: CFAbsoluteTime;
LDate: CFGregorianDate;
{ What we expect }
LStart, LEnd: TDateTime;
begin
{ Create the changes block. Keep it clean so far. }
Result := AllocMem(SizeOf(TYearlyChanges));
if FTZ <>
nil then
begin
{ Encode 1st Jan of the given year into Absolute time }
with LDate
do
begin
year := AYear;
month := MonthJanuary;
day := 1;
hour := 0;
minute := 0;
second := 0;
end;
{ Generate an absolute time (1st Jan Year) }
L1stJan := CFGregorianDateGetAbsoluteTime(LDate, FTZ);
{ Use CopyInfo but reverse the variables depending on the Northern/Southern hemispheres }
if not CFTimeZoneIsDaylightSavingTime(FTZ, L1stJan)
then
CopyInfo(FTZ, L1stJan, LStart, LEnd, Result.FBias, Result.FBiasWithDST)
else
CopyInfo(FTZ, L1stJan, LEnd, LStart, Result.FBiasWithDST, Result.FBias);
{ Fill the remaining parts of the structure }
LLocaleRef := CFLocaleCopyCurrent();
if LLocaleRef <>
nil then
begin
{ Obtain localized names of the locale (normal and DST) }
try
Result.FName := TCFString(CFTimeZoneCopyLocalizedName(FTZ,
kCFTimeZoneNameStyleStandard, LLocaleRef)).AsString(True);
Result.FDSTName := TCFString(CFTimeZoneCopyLocalizedName(FTZ,
kCFTimeZoneNameStyleDaylightSaving, LLocaleRef)).AsString(True);
finally
CFRelease(LLocaleRef);
end;
end else
begin
{ Fall back to std info }
Result.FName := TCFString(CFTimeZoneGetName(FTZ));
Result.FDSTName := Result.FName;
end;
Result.FStartOfDST := IncSecond(LStart, Result.FBias - Result.FBiasWithDST);
// Remove the save time from result
Result.FEndOfDST := IncSecond(LEnd, Result.FBiasWithDST - Result.FBias);
// Add the save time to result
end;
end;
{$ELSEIF defined(POSIX)}
var
LComp: tm;
LTime: time_t;
LLastOffset, LDay: Integer;
LIsSecondCycle, LIsStandard: Boolean;
LChars: TBytes;
begin
SetLength(LChars, 256);
{ Create the changes block. Keep it clean so far. }
Result := AllocMem(SizeOf(TYearlyChanges));
if (SizeOf(LongInt) = 4)
and ((AYear < 1970)
or (AYear > 2037))
then
Exit;
// Not supported
{ Generate a value that starts with 1st of the current year }
FillChar(LComp, SizeOf(LComp), 0);
LComp.tm_mday := 1;
LComp.tm_year := AYear - 1900;
LTime := mktime(LComp);
if LTime = -1
then
Exit;
// Some error occured!
{ Unknown DST information. Quit. }
if LComp.tm_isdst < -1
then
Exit;
{ Check if the DST or STD time was in effect at 1st Jan }
LIsStandard := LComp.tm_isdst = 0;
{ Prepare to iterate over the year }
LLastOffset := LComp.tm_gmtoff;
LIsSecondCycle := false;
{ Initialize info, in some locales like Russia and in some years the clock is not changed for daylight saving }
Result.FStartOfDST := IncSecond(FileDateToDateTime(LTime), LLastOffset - LComp.tm_gmtoff);
Result.FEndOfDST := Result.FStartOfDST;
Result.FBias := LLastOffset;
Result.FDSTName := '
';
Result.FBiasWithDST := LLastOffset;
strftime(MarshaledAString(LChars), Length(LChars), '
%Z', LComp);
// DO NOT LOCALIZE
Result.FName := Trim(TEncoding.UTF8.GetString(LChars));
//<----- Korrektur Alter Code----> // Result.FName := Utf8ToString(LChars);
for LDay := 0
to DaysInAYear(AYear) - 1
do
begin
{ Skip to next day }
Inc(LTime, SecsPerDay);
{ Decompose the time }
if localtime_r(LTime, LComp) <> @LComp
then
Exit;
if LComp.tm_gmtoff <> LLastOffset
then
begin
{ We found the day when the time change occured. Serach the hour now. }
repeat
Dec(LTime, SecsPerHour);
{ Decompose the time }
if localtime_r(LTime, LComp) <> @LComp
then
Exit;
until LComp.tm_gmtoff = LLastOffset;
{ Search for the minute }
repeat
Inc(LTime, SecsPerMin);
{ Decompose the time }
if localtime_r(LTime, LComp) <> @LComp
then
Exit;
until LComp.tm_gmtoff <> LLastOffset;
{ Generate the time zone abbreviation }
strftime(MarshaledAString(LChars), Length(LChars), '
%Z', LComp);
// DO NOT LOCALIZE
if LIsStandard
then
begin
{ We were in the standard period }
Result.FStartOfDST := IncSecond(FileDateToDateTime(LTime), LLastOffset - LComp.tm_gmtoff);
Result.FBias := LLastOffset;
Result.FDSTName := Trim(TEncoding.UTF8.GetString(LChars));
//<----- Korrektur Alter Code----> //Result.FDSTName := Utf8ToString(LChars);
end else
begin
{ We were in the DST period }
Result.FEndOfDST := IncSecond(FileDateToDateTime(LTime), LLastOffset - LComp.tm_gmtoff);
Result.FBiasWithDST := LLastOffset;
Result.FName := Trim(TEncoding.UTF8.GetString(LChars));
//<----- Korrektur Alter Code----> //Result.FName := Utf8ToString(LChars);
end;
{ Set the last offset }
LLastOffset := LComp.tm_gmtoff;
LIsStandard :=
not LIsStandard;
{ Die if this is the second cycle }
if LIsSecondCycle
then
Exit
else
LIsSecondCycle := true;
end;
end;
end;
{$ENDIF}