AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Programmierung allgemein Cross-Platform-Entwicklung Delphi Lokale Windows Zeitzone in IANA format ermitteln
Thema durchsuchen
Ansicht
Themen-Optionen

Lokale Windows Zeitzone in IANA format ermitteln

Ein Thema von QuickAndDirty · begonnen am 7. Feb 2020 · letzter Beitrag vom 8. Feb 2020
Antwort Antwort
QuickAndDirty

Registriert seit: 13. Jan 2004
Ort: Hamm(Westf)
1.883 Beiträge
 
Delphi 12 Athens
 
#1

Lokale Windows Zeitzone in IANA format ermitteln

  Alt 7. Feb 2020, 08:52
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 = 'mapZonethen
      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.
Angehängte Dateien
Dateityp: pas uTZDBFunctions.pas (5,1 KB, 6x aufgerufen)
Dateityp: zip WindowsTimezones.zip (9,7 KB, 6x aufgerufen)
Andreas
Monads? Wtf are Monads?

Geändert von QuickAndDirty ( 7. Feb 2020 um 09:43 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von p80286
p80286

Registriert seit: 28. Apr 2008
Ort: Stolberg (Rhl)
6.659 Beiträge
 
FreePascal / Lazarus
 
#2

AW: Lokale Windows Zeitzone in IANA format ermitteln

  Alt 7. Feb 2020, 20:52
Könntest Du mal bitte das "IANA-Format" beschreiben? Ich kann dazu nichts finden.

Gruß
K-H
Programme gehorchen nicht Deinen Absichten sondern Deinen Anweisungen
R.E.D retired error detector
  Mit Zitat antworten Zitat
samso

Registriert seit: 29. Mär 2009
439 Beiträge
 
#3

AW: Lokale Windows Zeitzone in IANA format ermitteln

  Alt 8. Feb 2020, 14:53
Zeitzonen-Datenbank
  Mit Zitat antworten Zitat
Benutzerbild von p80286
p80286

Registriert seit: 28. Apr 2008
Ort: Stolberg (Rhl)
6.659 Beiträge
 
FreePascal / Lazarus
 
#4

AW: Lokale Windows Zeitzone in IANA format ermitteln

  Alt 8. Feb 2020, 18:31
Also wäre die Angabe für alle Rechner in Deutschland:
DE +5230+01322 Europe/Berlin +01:00 +02:00
und für Rechner in der Schweiz:
CH +4723+00832 Europe/Zurich +01:00 +02:00

Ist das richtig?

Gruß
K-H
Programme gehorchen nicht Deinen Absichten sondern Deinen Anweisungen
R.E.D retired error detector
  Mit Zitat antworten Zitat
QuickAndDirty

Registriert seit: 13. Jan 2004
Ort: Hamm(Westf)
1.883 Beiträge
 
Delphi 12 Athens
 
#5

AW: Lokale Windows Zeitzone in IANA format ermitteln

  Alt 8. Feb 2020, 19:54
Aktuell ist das IANA-format meist
"Europe/Berlin"
"Europe/Paris"
"America/Buenos_Aires"
&c.

Aber die IANA hat auch Aliase für viele der Zeitzonen!
Und sie führt auch die historischen Namen der Zeitzonen und in welchem Zeitraum sie galten.
"Europe/Dublin"
hieß beispielsweise mal
"Eire"

Die Windows Zeitzonen sind zunächst mal sehr US-zentrisch. Da sind viele Bundesstaaten angegeben als Zeitzonen-Bezeichnung usw....
UND
sie sind aufs übelste lokalisiert! Und weder IOS noch Android nutzen die Windows Zeitzonen!!!
Ich weiß nicht was Linux nutzt aber da besteht evtl. noch Verbesserungsbedarf.
Andreas
Monads? Wtf are Monads?

Geändert von QuickAndDirty ( 8. Feb 2020 um 20:05 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von p80286
p80286

Registriert seit: 28. Apr 2008
Ort: Stolberg (Rhl)
6.659 Beiträge
 
FreePascal / Lazarus
 
#6

AW: Lokale Windows Zeitzone in IANA format ermitteln

  Alt 8. Feb 2020, 20:00
Danke!

K-H
Programme gehorchen nicht Deinen Absichten sondern Deinen Anweisungen
R.E.D retired error detector
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 20:00 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz