Da plattformübergreifend meist mit IANA Zeitzonen gearbeitet wird und ich TZDB.pas von der IANA nutze, ist es notwendig dass ich Windowszeitzonen in IANA-Format ermitteln kann.
Ich will einfach nur zeigen wie ich das gemacht habe. Falls jemand Verbessungen hat nur her damit.
Wie sich herausgestellt hat liefert
TTimeZone.Local.ID und getTimeZoneInfo(TZI) immer nur die lokalisierte Bezeichnung
'Westwuropäische Zeit'.
Das hilft natürlich nicht, wenn man eine IANA kompatible Bezeichnung haben möchte.
Also ermittle ich die Zeitzone aus der Registry
Delphi-Quellcode:
Function GetCurrentTimeZoneID:
String;
Begin
{$IFDEF WIN32}
var Reg := TRegistry.Create(KEY_READ);
try
Reg.RootKey := HKEY_LOCAL_MACHINE;
if Reg.OpenKeyReadOnly('
SYSTEM\CurrentControlSet\Control\TimeZoneInformation')
then
result := GetSaveTimeZoneID( reg.GetDataAsString('
TimeZoneKeyName') );
finally
Reg.Free;
end;
{$ELSE}}
Result := TTimeZone.
Local.ID;
{$ENDIF}
End;
GetSaveTimeZoneID sucht nach der passenden IANA Zeitzone und liefert einen Leerstring zurrück wenn nichts passendes gefunden werden kann.
Man Braucht zwingend TZDB.PAS und TZDB.INC von der IANA damit das ganze funktioniert!
Für das eigene Projekt muss man die {$INCLUDE c:\... } Pfade anpassen. Es ist kein installierbares
Package!
Delphi-Quellcode:
function GetSaveTimeZoneID(aTimeZoneID:String): String;
begin
var hit:String := '';
for var i:integer := Low(CZones) to High(CZones) do
Begin
if SameText(CZones[i].FName, ATimeZoneID) then
begin
hit := CZones[i].FName;
break;
end;
End;
if hit = '' then
Begin
for var i:integer := Low(CAliases) to High(CAliases) do
Begin
if SameText(CAliases[i].FName, ATimeZoneID) then
begin
hit := CAliases[i].FAliasTo^.FName ;
break;
end;
End;
End;
if hit = '' then
Begin
for var i:integer := Low(CWindowsTimeZones) to High(CWindowsTimeZones) do
Begin
if SameText(CWindowsTimeZones[i].Windows_Name, ATimeZoneID) or
SameText(CWindowsTimeZones[i].IANA_Name, ATimeZoneID) or
SameText(CWindowsTimeZones[i].ISO3166_Name, ATimeZoneID) then
begin
hit := CZones[CWindowsTimeZones[i].CZoneIndex ].FName;
break;
end;
End;
End;
Result := hit;
end;
Die "WindowsTimezones.inc" Datei generiere ich aus der WindowsTimezones.XML Datei des CDLR Projekts von
Unicode.org .
Ich weiß das der Code dafür nicht so toll aussieht, aber ich wollte ihn an sich auch nur alle 6 Monate ausführen....er muss also auch nicht effizient sein.
Weil in TZDB.PAS die Typen alle Privat deklariert sind und keine TZDBTypes.Pas Datei existiert ...muss man die Typen und die Include-Datei TZDB.INC überall mit rein kopieren...
Wenn ihr euch die WindowsTimezones.inc nicht selbst generieren wollt braucht man den Codegenerator auch nicht.
Ich habe eine WindowsTimezones.inc angehängt.
Delphi-Quellcode:
implementation
{$R *.fmx}
uses TZDB;
type
{ Day type. Specifies the "relative" day in a month }
TDayType = (dtFixed, dtLastOfMonth, dtNthOfMonth, dtPredOfMonth);
{ Specifies the mode in which a time value is specified }
TTimeMode = (trLocal, trStandard, trUniversal);
{ Stores the information about the relative days }
TRelativeDay = record
case FDayType: TDayType of
dtFixed:
(FFixedDay: Word);
dtLastOfMonth:
(FLastDayOfWeek: Word);
dtNthOfMonth:
(FNthDayOfWeek: Word; FNthDayIndex: Word);
dtPredOfMonth:
(FPredDayOfWeek: Word; FPredDayIndex: Word);
end;
{ Pointer to a relative day }
PRelativeDay = ^TRelativeDay;
{ Defines a rule used for DST changes }
TRule = record
FInMonth: Word; { The month (1 - 12) when DST change occurs }
FOnDay: PRelativeDay; { Pointer to a TRelativeDay value }
FAt: Int64; { Time, in seconds }
FAtMode: TTimeMode; { Time relation mode }
FOffset: Int64; { Offset from GMT in seconds }
FFmtPart: string;
{ A symbolic string used later when building short TZ names }
end;
{ Pointer to a rule }
PRule = ^TRule;
{ Defines a rule that also has a validity date defined }
TYearBoundRule = record
FStart: Word; { The year in which the rule starts to apply }
FEnd: Word; { The year in which the rule ends to apply }
FRule: PRule; { A pointer to the actual rule }
end;
{ Pointer to a year-bound rule entry }
PYearBoundRule = ^TYearBoundRule;
{ Defines a rule family. If fact it is a set of rules combined under the same ID }
TRuleFamily = record
FCount: Integer; { Count of rule in the current family }
FFirstRule: PYearBoundRule;
{ Pointer to the first rule in a static array defined previously }
end;
{ A pointer to a rule family }
PRuleFamily = ^TRuleFamily;
{ A period of some years (for a zone) that defines specific DST rules and offsets }
TPeriod = record
FOffset: Integer; { GMT offset in seconds for this period of time }
FRuleFamily: PRuleFamily;
{ Pointer to the family if rules that apply to this period }
FFmtStr: string;
{ Format string that will get translated in certain conditions }
FUntilYear, FUntilMonth: Word; { Period is valid until this Year/Month }
FUntilDay: PRelativeDay; { Period is valid until this Day in Year/Month }
FUntilTime: Int64;
FUntilTimeMode: TTimeMode; { Time relation mode }
{ Period is valid until this time of day Day in Year/Month. In seconds }
end;
{ Pointer to a TPeriod }
PPeriod = ^TPeriod;
{ Defines a time-zone. }
TZone = record
FName: string; { Zone name (aka Europe/Romania, Europe/London etc) }
FCount: Integer; { Count of periods defined by this zone }
FFirstPeriod: PPeriod; { Pointer to the first TPeriod for this zone }
end;
{ Pointer to a zone object }
PZone = ^TZone;
{ Alias to a zone }
TZoneAlias = record
FName: string; { Name of the zone to alias }
FAliasTo: PZone; { Pointer to aliased zone }
end;
{$INCLUDE 'TZDB.inc'}
type
TWindowsTimeZone = record
Index: Integer;
Windows_Name: String;
IANA_Name: String;
ISO3166_Name: String;
CZoneIndex:Integer;
end;
procedure TIncFileGenForm.StartBTNClick(Sender: TObject);
Function IndexOfZoneName(AZoneName:String):Integer;
Begin
Result := -1;
for var i:integer := 0 to length(CZones)-1 do
Begin
if SameText(CZones[i].FName,AZoneName) then
Begin
Result := i;
break;
End;
End;
End;
Function IndexOfZoneAlias(AZoneName:String):Integer;
Begin
Result := -1;
for var i:Integer := 0 to length(CAliases)-1 do
Begin
if SameText(CAliases[i].FName,AZoneName) then
Begin
Result := i;
break;
End;
End;
End;
Function AliasIndexToCZoneIndex(aAliasIndex:Integer):Integer;
Begin
Result := IndexOfZoneName(CAliases[aAliasIndex].FAliasTo^.FName);
End;
Function IndexOfWindowsTimeZone(aWindowsTimeZones:TList<TWindowsTimeZone>; aIANA_Name,aISO3166_Name ,aWindows_Name:String):integer;
var Currentelement:TWindowsTimeZone;
Begin
Result := -1;
for var i:integer := 0 to aWindowsTimeZones.Count-1 do
Begin
Currentelement := aWindowsTimeZones[i];
if CurrentElement.IANA_Name.Equals(aIANA_Name) and
CurrentElement.Windows_Name.Equals(aWindows_Name) and
CurrentElement.ISO3166_Name.Equals(aISO3166_Name) then
Begin
Result := i;
break;
End;
End;
End;
Procedure EnterEntry(aWindowsTimeZones:TList<TWindowsTimeZone>; aIANA_Name, aISO3166_Name, aWindows_Name:String; CZoneIndex:Integer);
var CurrentWindowsTimeZone:TWindowsTimeZone;
Begin
if IndexOfWindowsTimeZone(aWindowsTimeZones, aIANA_Name,aISO3166_Name,aWindows_Name) = -1 then // Keine Doppelten
Begin
CurrentWindowsTimeZone.Index := aWindowsTimeZones.Count;
CurrentWindowsTimeZone.IANA_Name := aIANA_Name;
CurrentWindowsTimeZone.ISO3166_Name := aISO3166_Name;
CurrentWindowsTimeZone.Windows_Name := aWindows_Name;
CurrentWindowsTimeZone.CZoneIndex := CZoneIndex;
aWindowsTimeZones.Add(CurrentWindowsTimeZone);
End;
End;
procedure ReadDocument(aWindowsTimeZones:TList<TWindowsTimeZone>);
var
LDocument: IXMLDocument;
LNodeElement,root ,LSuplementData, LWindowsZones, LMapTimeZones,LCurrentTimeZone,NodeCData, NodeText: IXMLNode;
LTimeZoneList: IXMLNodeList;
FirstIndex:Integer;
IANA_NameList,IANA_Name, ISO3166_Name, Windows_Name:String;
IANA_NameArr:TArray<String>;
Begin
aWindowsTimeZones.Clear;
LDocument := TXMLDocument.Create(
TPath.Combine( TPath.GetDirectoryName(paramstr(0)), 'windowsZones.xml')
) as IXMLDocument;
root := LDocument.DocumentElement;
LSuplementData := LDocument.ChildNodes[root.NodeName];
LWindowsZones := LSuplementData.ChildNodes['windowsZones'];
LMapTimeZones := LWindowsZones.ChildNodes['mapTimezones'];
LTimeZoneList := LMapTimeZones.ChildNodes;
for var i:Integer := 0 to LTimeZoneList.Count-1 do
Begin
LCurrentTimeZone := LTimeZoneList[i];
If LCurrentTimeZone.NodeName = 'mapZone' then
Begin
IANA_NameList := LCurrentTimeZone.Attributes['type'];
IANA_NameArr := IANA_NameList.Split([' ']);
ISO3166_Name := LCurrentTimeZone.Attributes['territory'];
Windows_Name := LCurrentTimeZone.Attributes['other'];
for var j:integer := 0 to Length( IANA_NameArr )-1 do
Begin
IANA_Name := IANA_NameArr[j];
FirstIndex := IndexOfZoneName( IANA_Name );
If FirstIndex >= 0 then
Begin
EnterEntry(aWindowsTimeZones, IANA_Name, ISO3166_Name, Windows_Name, FirstIndex );
end// If FirstIndex >= 0 then
else
Begin
FirstIndex := IndexOfZoneAlias( IANA_Name );
If FirstIndex >= 0 then
Begin
FirstIndex := AliasIndexToCZoneIndex( FirstIndex );
EnterEntry(aWindowsTimeZones, IANA_Name, ISO3166_Name, Windows_Name, FirstIndex );
end// If FirstIndex >= 0 then
else
Begin
ShowMessage( IANA_Name + '@' + ISO3166_Name + '@' + Windows_Name );
end;// If FirstIndex >= 0 then // Else
end;// If FirstIndex >= 0 then // Else
end;
End;
End;
End;
procedure WriteIncludeFile(aWindowsTimeZones:TList<TWindowsTimeZone>);
var sOut:String;
begin
var sw := TStreamWriter.Create(
TPath.Combine( TPath.GetDirectoryName(paramstr(0)), 'WindowsTimezones.inc'),
False,{Append=False}
TEncoding.UTF8
);
try
SW.WriteLine('var');
SW.WriteLine(' { This array contains Window Timezone aliases. }');
SW.WriteLine(' CWindowsTimeZones: array[0 .. '+(aWindowsTimeZones.count-1).tostring+'] of TWindowsTimeZone = (');
for var i:Integer := 0 to aWindowsTimeZones.count-1 do
Begin
sOut := Format(' (Index:%d; Windows_Name:%s; IANA_Name:%s; ISO3166_Name:%s; CZoneIndex:%d ),',
[aWindowsTimeZones[i].Index,
QuotedStr(aWindowsTimeZones[i].Windows_Name) ,
QuotedStr(aWindowsTimeZones[i].IANA_Name),
QuotedStr(aWindowsTimeZones[i].ISO3166_Name),
aWindowsTimeZones[i].CZoneIndex] );
if i = aWindowsTimeZones.count-1 then
sout := sOut.Remove( sOut.Length-1 );
SW.Writeline( sOut );
end;
SW.WriteLine( ');');
finally
SW.Free;
end;
End;
begin
var WindowsTimeZones := TList<TWindowsTimeZone>.create;
try
ReadDocument(WindowsTimeZones);
WriteIncludeFile(WindowsTimeZones);
finally
WindowsTimeZones.Free;
end;
ShowMessage('Document Done');
end;
Ich bekomme jetzt zumindest Crossplatform überall IANA Zeitzonen IDs...
Kein Mensch weiß warum Windows das von alleine nicht kann.
Hoffe es hilft jemand mit dem selben Problem. Ihr habt mir bei so vielen Problemen geholfen.