Zeitsynchronisation im NT-Netzwerk

Veröffentlicht: 01. Okt 2002 | Aktualisiert: 10. Nov 2004

Von Mathias Schiffer

WindowsNT-, Windows-2000- und Windows-XP-Rechner können Datum und Uhrzeit eines anderen Rechners im Netzwerk ermitteln. Mit dieser Funktion sind Sie in der Lage, Daten und Uhrzeiten aller NT-Rechner in einem Netzwerk zu synchronisieren.

Auf dieser Seite

 Der Schlüssel zur Zeitabfrage: NetRemoteTOD
 Date-Variable mit Lokalzeit berechnen
 Kompletter Beispielcode

Gleich zu Beginn die Einschränkungen dieses MSDN Quickies: Für die Abfrage von Datum und Uhrzeit von einem anderen Rechner im Netzwerk müssen beide beteiligten Rechner einem Windows-NT-Betriebssystem ab Version 3.51 ausgestattet sein. Dies schließ;t Windows 2000 und Windows XP und Folgeversionen ein. Mit Windows 95, Windows 98 und Windows Me funktioniert das vorgestellte Verfahren nicht.

Der Benutzer am auskunftsersuchenden Rechner muss zwar keiner bestimmten Benutzergruppe angehören, muss aber natürlich über ein Zugangsrecht für den auskunftsgebenden Rechner verfügen. Bereits der Gast-Account auf dem auskunftserteilenden Rechner genügt.

Der Schlüssel zur Zeitabfrage: NetRemoteTOD

Im NT-Netzwerk geben sich Rechner bezüglich ihres lokalen Datums auskunftsfreudig: Die API-Funktion NetRemoteTOD ruft von einem im ersten Parameter angegebenen Rechner im Netzwerk die aktuelle Zeit ab. Sie kopiert die Zeit in einen von ihr angelegten Speicherbereich, der im Aufbau dem einer TIME_OF_DAY_INFO-Struktur entspricht.

Den Rechnernamen müssen Sie hierbei in UNC-Form, also mit einleitendem "\\" angeben - beispielsweise "\\TIMESERVER". Für lokale Tests können Sie statt eines entfernten oder des eigenen Rechnernamens auch einfach vbNullString (ohne einleitendes "\\") verwenden, um probeweise Ihre lokale Maschine als Zeitlieferanten anzusprechen.

Im zweiten Parameter, der als Referenzparameter verwendet wird, gibt NetRemoteTOD einen Zeiger auf den von ihr reservierten Speicherbereich mit den gesuchten Informationen zurück. Mithilfe dieses Zeigers und der API-Funktion CopyMemory kann der Speicherbereich in eine von uns zur Verfügung gestellte Variable des Typs TIME_OF_DAY_INFO kopiert werden und wie von VB gewohnt einfach ausgelesen werden.

Nach dieser Kopieraktion wird der von NetRemoteTOD reservierte Speicher mithilfe der Funktion NetApiBufferFree wieder freigegeben, um kein Speicherleck zu verursachen.

In den so gewonnenen TIME_OF_DAY_INFO-Informationen finden sich nun neben i.A. weniger interessanten Werten auch Datum und Uhrzeit des entfernten Rechners als GMT-Angabe ("Greenwich Mean Time", auch bekannt als UTC = "Universal Coordinated Time").

Ebenso finden wir darin die Zeitverschiebung gegenüber der GMT-Zeitzone, die auf dem abgefragten Rechner durch entsprechende Zeitzoneneinstellungen konfiguriert wurde. Ein erstes Anwendungsbeispiel soll das Vorgehen aufzeigen:

' --- Notwendige API-Deklarationen:
Private Const NERR_Success As Long = 0&
Private Type TIME_OF_DAY_INFO
  tod_elapsedt  As Long ' GMT-Sekundenzähler ab 01.01.1970
  tod_msecs     As Long ' Millisekunden seit Systemstart
  tod_hours     As Long ' Stundenangabe
  tod_mins      As Long ' Minutenangabe
  tod_secs      As Long ' Sekundenangabe
  tod_hunds     As Long ' Hunderstelsekundenangabe
  tod_timezone  As Long ' GMT-Zeitzonenverschiebung in Minuten
  tod_tinterval As Long ' Zenhntel Millisekunden der Systemuhr
  tod_day       As Long ' Tagesangabe
  tod_month     As Long ' Monatsangabe
  tod_year      As Long ' Jahresangabe
  tod_weekday   As Long ' Wochentagsangabe (0=Sonntag, ...)
End Type
Private Declare Function NetRemoteTOD _
  Lib "netapi32" ( _
  ByRef UNCServerName As Byte, _
  ByRef lpBuffer As Long _
  ) As Long
Private Declare Sub CopyMemory _
  Lib "kernel32" Alias "RtlMoveMemory" ( _
  ByRef Destination As Any, _
  ByRef Source As Any, _
  ByVal NumberOfBytes As Long)
Private Declare Function NetApiBufferFree _
  Lib "netapi32" ( _
  ByVal lpBuffer As Long _
  ) As Long
' --- Ein einfaches Anwendungsbeispiel:
Dim TODI As TIME_OF_DAY_INFO  ' Zeitinformationen
Dim baRemoteServer() As Byte  ' Byte-Array für Servernamen
Dim ptrBuffer As Long         ' Zeiger auf reservierten Speicher
Dim lRet As Long
  ' Unicode-String in ein Byte-Array kopieren
  baRemoteServer = vbNullString & vbNullChar ' lokale Maschine
  ' Aufruf NetRemoteTOD, Ergbenis wird bei Erfolg in
  ' einen Speicherbereich geschrieben, auf den ptrBuffer
  ' zeigt:
  lRet = NetRemoteTOD(baRemoteServer(0), ptrBuffer)
  ' War der Aufruf von NetRemoteTOD erfolgreich?
  If lRet = NERR_Success Then
    ' Die Informationan aus dem von NetRemoteTOD reservierten
    ' Speicherbereich in eine TIME_OF_DAY_INFO-Variable kopieren:
    CopyMemory TODI, ByVal ptrBuffer, Len(TODI)
    ' Den reservierten Speicherbereich freigeben:
    NetApiBufferFree ptrBuffer
    ' Auswertung des Ergebnisses
    With TODI
      MsgBox "GMT-Uhrzeit auf dem abgefragten Rechner:" _
        & vbNewLine _
        & .tod_day & "." & .tod_month & "." & .tod_year _
        & ", " _
        & .tod_hours & ":" & .tod_mins & ":" & .tod_secs _
        & " Uhr (GMT)" _
        & vbNewLine & vbNewLine _
        & "GMT-Zeitverschiebung des abgefragten Rechners:" _
        & vbNewLine & "GMT" & IIf(-.tod_timezone > 0, "+", "") _
        & -.tod_timezone & " Minuten" _
        , vbInformation, "Abfrage erfolgreich"
    End With
  Else
    MsgBox "Die Abfrage mittels NetRemoteTOD war erfolglos.", _
           vbCritical, "Abfrage erfolglos"
  End If

Natürlich kann diese Information bereits interessant sein. Sehr handlich ist sie aber noch nicht. Zudem wäre oftmals viel interessanter zu wissen, welcher Uhrzeit die erlangte Information auf dem lokalen System entspricht - das ja durchaus einer anderen Zeitzoneneinstellung unterworfen sein kann.

 

Date-Variable mit Lokalzeit berechnen

Die VB-Funktionen DateSerial und TimeSerial machen es einfach, die GMT-Zeitangabe aus der gewonnenen TIME_OF_DAY_INFO-Struktur in eine Variable des VB-Datentype Date zu konvertieren:

Dim DateTimeGMT As Date
  With TODI
    DateTimeGMT = DateSerial(.tod_year, .tod_month, .tod_day) + _
                  TimeSerial(.tod_hours, .tod_mins, .tod_secs)
  End With

Nun fehlt nur noch die Zeitverschiebung des lokalen Rechners gegenüber der GMT-Zeit, um mithilfe der VB-Funktion DateAdd die entsprechende Anzahl an Minuten addieren bzw. subtrahieren zu können. Diese Kenntnis liefert uns die API-Funktion GetTimeZoneInformation, auf die im MSDN Quickie Zeitzoneninformationen ermitteln bereits ausführlich eingegangen wurde, so dass das Beispiel ohne langwierige Erklärungen weitergeführt werden kann:

' --- Notwendige API-Deklarationen:
Private Const TIME_ZONE_ID_DAYLIGHT As Long = 2&
Private Type SYSTEMTIME
  wYear As Integer
  wMonth As Integer
  wDayOfWeek As Integer
  wDay As Integer
  wHour As Integer
  wMinute As Integer
  wSecond As Integer
  wMilliseconds As Integer
End Type
Private Type TIME_ZONE_INFORMATION
  Bias As Long
  StandardName(1 To 64) As Byte
  StandardDate As SYSTEMTIME
  StandardBias As Long
  DaylightName(1 To 64) As Byte
  DaylightDate As SYSTEMTIME
  DaylightBias As Long
End Type
Private Declare Function GetTimeZoneInformation _
  Lib "kernel32" ( _
  ByRef lpTZI As TIME_ZONE_INFORMATION _
  ) As Long
' --- Ermittlung des lokalen GMT-Offsets in Minuten:
Dim GMTOffsetMinutes As Long
Dim TZI As TIME_ZONE_INFORMATION
  If GetTimeZoneInformation(TZI) = TIME_ZONE_ID_DAYLIGHT Then
    GMTOffsetMinutes = TZI.Bias + TZI.DaylightBias
  Else
    GMTOffsetMinutes = TZI.Bias + TZI.StandardBias
  End If

Hiermit können wir nun die bereits angesprochene DateAdd-Funktion bemühen, um die empfangene GMT-Zeit in die Lokalzeit unseres Rechners zu überführen:

Dim DateTime As Date
  DateTime = DateAdd("n", -GMTOffsetMinutes, DateTimeGMT)
  ' Setzen der ermittelten Zeit als lokale Systemzeit:
  ' Date = DateTime

 

Kompletter Beispielcode

Abschließ;end fassen wir alle obigen Schritte noch einmal in einem kompletten Beispielcode zusammen, den Sie per Copy & Paste in Ihre Anwendungen integrieren können.

Tipp: Markieren Sie den gesamten Code und fügen Sie ihn zunächst in Write.exe ein. Kopieren Sie den Code von dort aus in Ihren Visual Basic Editor, um den Verlust von Zeilenumbrüchen zu vermeiden.

' -------------------------------------------------------
' Beispiel zur Anwendung von NetRemoteTOD im NT-Netzwerk
' Copyright © 2002 by Mathias Schiffer, AixSoft Software
' -------------------------------------------------------
' --- Notwendige API-Deklarationen
Private Const NERR_Success          As Long = 0&
Private Const TIME_ZONE_ID_DAYLIGHT As Long = 2&
Private Type TIME_OF_DAY_INFO
  tod_elapsedt  As Long ' GMT-Sekundenzähler ab 01.01.1970
  tod_msecs     As Long ' Millisekunden seit Systemstart
  tod_hours     As Long ' Stundenangabe
  tod_mins      As Long ' Minutenangabe
  tod_secs      As Long ' Sekundenangabe
  tod_hunds     As Long ' Hunderstelsekundenangabe
  tod_timezone  As Long ' GMT-Zeitzonenverschiebung in Minuten
  tod_tinterval As Long ' Zenhntel Millisekunden der Systemuhr
  tod_day       As Long ' Tagesangabe
  tod_month     As Long ' Monatsangabe
  tod_year      As Long ' Jahresangabe
  tod_weekday   As Long ' Wochentag (0=Sonntag, ...)
End Type
Private Type SYSTEMTIME ' Notwendig für TIME_ZONE_INFORMATION
  wYear As Integer
  wMonth As Integer
  wDayOfWeek As Integer
  wDay As Integer
  wHour As Integer
  wMinute As Integer
  wSecond As Integer
  wMilliseconds As Integer
End Type
Private Type TIME_ZONE_INFORMATION
  Bias As Long
  StandardName(1 To 64) As Byte
  StandardDate As SYSTEMTIME
  StandardBias As Long
  DaylightName(1 To 64) As Byte
  DaylightDate As SYSTEMTIME
  DaylightBias As Long
End Type
Private Declare Function NetRemoteTOD _
  Lib "netapi32" ( _
  ByRef UNCServerName As Byte, _
  ByRef lpBuffer As Long _
  ) As Long
Private Declare Function GetTimeZoneInformation _
  Lib "kernel32" ( _
  ByRef lpTZI As TIME_ZONE_INFORMATION _
  ) As Long
Private Declare Function NetApiBufferFree _
  Lib "netapi32" ( _
  ByVal lpBuffer As Long _
  ) As Long
Private Declare Sub CopyMemory _
  Lib "kernel32" Alias "RtlMoveMemory" ( _
  ByRef Destination As Any, _
  ByRef Source As Any, _
  ByVal NumberOfBytes As Long)
' -------------------------------
' --- Code: GetRemoteDateTime ---
' -------------------------------
Private Function GetRemoteDateTime( _
                          ByVal RemoteServerName As String, _
                          ByRef RemoteDateTime As Date _
                          ) As Boolean
' ---------------------------------------------------------------
' Ermittelt Datum und Uhrzeit auf einem anderen Rechner im
' Netzwerk. Abfragender und abgefragter Rechner müssen
' Windows NT/2000/XP-Rechner sein. Die Zeitzoneneinstellung
' des abgefragten Rechners ist hierbei irrelevant, die Zeit
' wird anhand der Zeitzoneneinstellungen des abfragenden
' Rechners von GMT in lokale Zeit umgerechnet.
'
' Parameter:
'
' RemoteserverName: Name des abzufragenden Rechners im Netzwerk
'                   (Übergabe in UNC-Notation - z.B.
'                   "\\TIMESERVER" - ist zulässig, aber nicht
'                   notwendig).
'
' RemoteDateTime:   Datum und Uhrzeit des abgefragten Rechners
'                   bei Erfolg. Bei Fehlschlag bleibt dieser
'                   ByRef-Parameter unverändert.
'
' Rückgabewert:     True bei Erfolg, False bei Fehlschlag.
' ---------------------------------------------------------------
Dim TZI As TIME_ZONE_INFORMATION ' Zeitzonen-Informationen
Dim TODI As TIME_OF_DAY_INFO     ' Tageszeit-Informationen
Dim baRemoteServer() As Byte     ' Byte-Array für Servernamen
Dim ptrBuffer As Long            ' Zeiger auf Speicherbereich
Dim DateTimeGMT As Date          ' Abgefragte Zeit als GMT-Date
Dim lRet As Long
  ' Format der Übergabe von RemoteServerName überprüfen:
  If LenB(RemoteServerName) = 0 Then
    RemoteServerName = vbNullString
  Else
    ' RemoteServerName muss mit "\\" angegeben werden:
    If Left$(RemoteServerName, 1) <> "\" Then
      RemoteServerName = "\" & RemoteServerName
    End If
    If Left$(RemoteServerName, 2) <> "\\" Then
      RemoteServerName = "\" & RemoteServerName
    End If
  End If
  ' Unicode-String in ein Byte-Array kopieren
  baRemoteServer = RemoteServerName & vbNullChar
  ' Aufruf NetRemoteTOD, Ergbenis wird bei Erfolg in
  ' einen Speicherbereich geschrieben, auf den ptrBuffer
  ' zeigt:
  lRet = NetRemoteTOD(baRemoteServer(0), ptrBuffer)
  If lRet = NERR_Success Then ' NetRemoteTOD erfolgreich
    ' Die Informationan aus dem von NetRemoteTOD reservierten
    ' Speicherbereich in eine TIME_OF_DAY_INFO-Variable kopieren:
    CopyMemory TODI, ByVal ptrBuffer, Len(TODI)
    ' Den reservierten Speicherbereich freigeben:
    NetApiBufferFree ptrBuffer
    With TODI
      ' Die Struktur TIME_OF_DAY_INFO enthält GMT-Zeitangaben
      ' zzgl. der Zeitzonenverschiebung des abgefragten Rechners.
      ' Erfassung der GMT-Zeitangabe als Date-Variable:
      DateTimeGMT = DateSerial(.tod_year, .tod_month, .tod_day) _
                  + TimeSerial(.tod_hours, .tod_mins, .tod_secs)
      ' GMT-Zeit je nach Sommer-/Normalzeit beim Abrufenden
      ' in lokale Zeit umrechnen:
      If GetTimeZoneInformation(TZI) = TIME_ZONE_ID_DAYLIGHT Then
        ' Umrechnung bei Sommerzeit
        RemoteDateTime = DateAdd("n", -(TZI.Bias + _
                                 TZI.DaylightBias), _
                                 DateTimeGMT)
      Else
        ' Umrechnung bei Normalzeit
        RemoteDateTime = DateAdd("n", -(TZI.Bias + _
                                 TZI.StandardBias), _
                                 DateTimeGMT)
      End If
      ' Erfolg als Rückgabewert:
      GetRemoteDateTime = True
    End With
  End If
End Function
' --------------------------
' --- Anwendungsbeispiel ---
' --------------------------
Dim dtRemoteDateTime As Date
Dim sRemoteServerName As String
Dim lSekundenUnterschied As Long
  sRemoteServerName = "\\TIMESERVER" ' Name eines NT/2000/XP-
                                     ' Rechners im Netzwerk
  If GetRemoteDateTime(sRemoteServerName, _
                       dtRemoteDateTime) = True Then
    ' Unterschied zur lokalen Zeit ermitteln:
    lSekundenUnterschied = Abs(DateDiff("s", _
                                        dtRemoteDateTime, Now))
    ' Ausgabe des Ergebnisses und Nachfrage, ob die lokale
    ' Zeit der ermittelten Zeit angepasst werden soll:
    If MsgBox("""" & sRemoteServerName & """" & vbNewLine _
          & "meldet als Uhrzeit:" & vbNewLine _
          & Format$(dtRemoteDateTime, "dd.mm.yyyy hh:nn:ss") _
          & vbNewLine & "Unterschied zu Ihrem System: " _
          & CStr(lSekundenUnterschied) & " Sekunden." _
          & vbNewLine & vbNewLine _
          & "Möchten Sie Ihre lokale Zeit anpassen?" _
          , vbQuestion Or vbYesNo, "Zeitermittlung") = vbYes Then
      ' Remote-Zeit erneut abholen und aktuell angleichen 
      ' (genauer als zweifache Rundung durch DateDiff/DateAdd):
      If GetRemoteDateTime(sRemoteServerName, _
                           dtRemoteDateTime) = True Then
        Date = dtRemoteDateTime
        MsgBox "Ihre lokale Zeit wurde angepasst.", vbInformation
      Else
        ' Der erneute Versuch war erfolglos
        MsgBox "Der Zeitgeber steht nicht mehr zur Verfügung.", _
               vbInformation
      End If
    End If
  Else
    MsgBox "Datum und Uhrzeit des Rechners " & vbNewLine _
           & """" & sRemoteServerName & """" & vbNewLine _
           & "konnten nicht ausgelesen werden." _
           , vbCritical
  End If
' -------------------------------------------------------