|
|
![]() |
|
||
|
|
|||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||
| Visual Basic Locale/Regionalization
Routines Identifying Time Zones Using the Time Zone Bias |
||
| Posted: | Monday July 08, 2002 | |
| Updated: | Monday December 26, 2011 | |
| Applies to: | VB4-32, VB5, VB6 | |
| Developed with: | VB6, Windows XP Pro | |
| OS restrictions: | None | |
| Author: | VBnet - Randy Birch | |
|
Related: |
WM_TIMECHANGE: Detect System Changes to the Date/Time RegQueryValueEx: Determine Windows Last Shutdown Date and Time RegQueryValueEx: Identify Time Zones by Time Zone Bias EnumDateFormats: Regional Locale Date Settings EnumTimeFormats: Regional Locale Time Settings GetLocaleInfo: Regional Locale Date Settings |
|
| Prerequisites |
| None. |
|
|
I
received an interesting request for time zone information. The code at
SetSystemTime: Date and Time Synchronization to a Remote Server
shows how to retrieve a remote server's TIME_OF_DAY_INFO, and how to
synchronize machines to a remote server. One member of the
TIME_OF_DAY_INFO structure is tod_timezone ... the Bias from GMT for the
remote server. The request was for a means to take this time and
determine what the actual time zone was for the server.
After scanning the MSDN there appeared no direct way to pass a Bias and have the system return the zones currently represented by that bias. So using a few registry reads, a custom UDT for the data, and a simple UDT array lookup loop I was able to construct the code that would (somewhat) satisfy the request. The primary issue that can not be resolved with just the GMT bias is that of multiple time zones each having the same Bias. As the demo shows, for a Bias of 240 minutes during daylight saving time there are three different time zones that satisfy the Bias ... Eastern Standard Time, SA Pacific Standard Time and US Eastern Standard Time. The code is straight forward: the initial routine checks the operating system version and in order to determine the correct registry key to read. It then checks and sets a flag indicating whether, according to the settings of the local machine, the machine is currently in "Daylight Saving Time mode". This information is used because on the local machine, the Bias minutes returned from the registry in the REG_TIME_ZONE_INFORMATION's Bias member represents the Standard Time bias from GMT, yet the data returned from the synchronization TIME_OF_DAY_INFO always reflects the current system Bias which changes depending on whether Standard or Daylight time is currently in force. For example, if a local machine in England is in standard time and a machine in Ontario was queried using the code from SetSystemTime: Date and Time Synchronization to a Remote Server, the Bias returned as tod_timezone for the Ontario machine would be 300 (5 hours). This is the same data returned as the REG_TIME_ZONE_INFORMATION's Bias member in this demo. But, when the local machine is in Daylight Saving Time, the remote Ontario machine returns 240 (4 hours). Thus attempting to compare the tod_timezone with the Bias member without taking DST into account would return the wrong set of time zones when DST was in force. Therefore, the demo determines if the local machine is running on DST, and if so applies the REG_TIME_ZONE_INFORMATION's DaylightBias to the base Bias. Thus the 240 minute Bias for Ontario during DST is correctly reflected as the registry's Eastern Standard Time member. However, there is a caveat. Depending on the location of the machine on the WAN, the local machine's Standard/Daylight changeover dates may not necessarily be the same as the distant WAN server (ie someone reported that Australia changeover dates are a week different than North America's). Therefore, although this code can provide the correct time zone when both the local and remote machine's are both in either Standard or Daylight times, judicious use of the method is required to ensure the code is not used during the period one machine has changed over and the other has not. You may also notice that, even though the current time setting was Daylight Saving Time when this demo was created, the names returned from the code below are all stating Standard Time. If it is DST in Ontario, one would think the string returned from the registry should be "Eastern Daylight Saving Time", not "Eastern Standard Time" as shown. This naming anomaly is due to the way the strings are stored in the registry ... all registry time zone primary keys - the ones enumerated and listed above - contain the phrase "Standard Time" (look in the registry under HKEY_LOCAL_MACHINE and the keys listed as constants in the code below). Under each set of time zone keys is a DLT subkey which contains the actual name of the Daylight Saving time for each time zone. I had initially thought of leaving the end user to apply a simple Replace() call when the IsDaylightSavingTime flag was set, to change "Standard" to "Daylight", but on examination of a few registry keys I found at least one "Daylight Saving" name that did not parallel the "Standard Time" name. While "Eastern Standard Time" becomes "Eastern Daylight Time", interestingly (according to the registry) "Israel Standard Time" becomes "Jerusalem Daylight Time". Now, if Israelis could live with "Israel Daylight Time", then performing a Replace() easily takes care of assuring the names reflect DST if required. But where precision is the order of the day, I leave to you, the user, the task of retrieving the proper Daylight Time string from the registry should it actually be required. |
| BAS Module Code |
| None. |
|
|
| Form Code |
|
|
| Add a label (Label1) to hold the return value representing the Bias time, a command button (Command1) a text box (Text1) and two lists (List1, List2) to a form. Other labels are optional. Add the following code: |
|
|
Option Explicit '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Copyright ©1996-2011 VBnet/Randy Birch, All Rights Reserved. ' Some pages may also contain other copyrights by the author. '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Distribution: You can freely use this code in your own ' applications, but you may not reproduce ' or publish this code on any web site, ' online service, or distribute as source ' on any media without express permission. '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 'IsDaylightSavingTime flag
Private BiasAdjust As Boolean
' results UDT
Private Type TZ_LOOKUP_DATA
TimeZoneName As String
Bias As Long
IsDST As Boolean
End Type
Private tzinfo() As TZ_LOOKUP_DATA
'holds the correct key for the OS version
Private sTzKey As String
'windows constants and declares
Private Const TIME_ZONE_ID_UNKNOWN As Long = 1
Private Const TIME_ZONE_ID_STANDARD As Long = 1
Private Const TIME_ZONE_ID_DAYLIGHT As Long = 2
Private Const TIME_ZONE_ID_INVALID As Long = &HFFFFFFFF
Private Const VER_PLATFORM_WIN32_NT = 2
Private Const VER_PLATFORM_WIN32_WINDOWS = 1
'registry constants
Private Const SKEY_NT = "SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones"
Private Const SKEY_9X = "SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones"
Private Const HKEY_LOCAL_MACHINE = &H80000002
Private Const ERROR_SUCCESS = 0
Private Const REG_SZ As Long = 1
Private Const REG_BINARY = 3
Private Const REG_DWORD As Long = 4
Private Const STANDARD_RIGHTS_READ As Long = &H20000
Private Const KEY_QUERY_VALUE As Long = &H1
Private Const KEY_ENUMERATE_SUB_KEYS As Long = &H8
Private Const KEY_NOTIFY As Long = &H10
Private Const SYNCHRONIZE As Long = &H100000
Private Const KEY_READ As Long = ((STANDARD_RIGHTS_READ Or _
KEY_QUERY_VALUE Or _
KEY_ENUMERATE_SUB_KEYS Or _
KEY_NOTIFY) And _
(Not SYNCHRONIZE))
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 FILETIME
dwLowDateTime As Long
dwHighDateTime As Long
End Type
Private Type REG_TIME_ZONE_INFORMATION
Bias As Long
StandardBias As Long
DaylightBias As Long
StandardDate As SYSTEMTIME
DaylightDate As SYSTEMTIME
End Type
Private Type TIME_ZONE_INFORMATION
Bias As Long
StandardName(0 To 63) As Byte
StandardDate As SYSTEMTIME
StandardBias As Long
DaylightName(0 To 63) As Byte
DaylightDate As SYSTEMTIME
DaylightBias As Long
End Type
Private Type OSVERSIONINFO
OSVSize As Long
dwVerMajor As Long
dwVerMinor As Long
dwBuildNumber As Long
PlatformID As Long
szCSDVersion As String * 128
End Type
Private Declare Function GetVersionEx Lib "kernel32" _
Alias "GetVersionExA" _
(lpVersionInformation As OSVERSIONINFO) As Long
Private Declare Function GetTimeZoneInformation Lib "kernel32" _
(lpTimeZoneInformation As TIME_ZONE_INFORMATION) As Long
Private Declare Function RegOpenKeyEx Lib "advapi32.dll" _
Alias "RegOpenKeyExA" _
(ByVal hKey As Long, _
ByVal lpsSubKey As String, _
ByVal ulOptions As Long, _
ByVal samDesired As Long, _
phkResult As Long) As Long
Private Declare Function RegQueryValueEx Lib "advapi32.dll" _
Alias "RegQueryValueExA" _
(ByVal hKey As Long, _
ByVal lpszValueName As String, _
ByVal lpdwReserved As Long, _
lpdwType As Long, _
lpData As Any, _
lpcbData As Long) As Long
Private Declare Function RegQueryInfoKey Lib "advapi32.dll" _
Alias "RegQueryInfoKeyA" _
(ByVal hKey As Long, _
ByVal lpClass As String, _
lpcbClass As Long, _
ByVal lpReserved As Long, _
lpcsSubKeys As Long, _
lpcbMaxsSubKeyLen As Long, _
lpcbMaxClassLen As Long, _
lpcValues As Long, _
lpcbMaxValueNameLen As Long, _
lpcbMaxValueLen As Long, _
lpcbSecurityDescriptor As Long, _
lpftLastWriteTime As FILETIME) As Long
Private Declare Function RegQueryValueExString Lib "advapi32.dll" _
Alias "RegQueryValueExA" _
(ByVal hKey As Long, _
ByVal lpValueName As String, _
ByVal lpReserved As Long, _
lpType As Long, _
ByVal lpData As String, _
lpcbData As Long) As Long
Private Declare Function RegEnumKey Lib "advapi32.dll" _
Alias "RegEnumKeyA" _
(ByVal hKey As Long, _
ByVal dwIndex As Long, _
ByVal lpName As String, _
ByVal cbName As Long) As Long
Private Declare Function RegCloseKey Lib "advapi32.dll" _
(ByVal hKey As Long) As Long
Private Declare Function lstrlenW Lib "kernel32" _
(ByVal lpString As Long) As Long
Private Sub Form_Load()
With Command1
.Caption = "Load TZ Array"
.Enabled = True
End With
With Command2
.Caption = "Lookup Time Zone"
.Enabled = False
End With
With Text1
.Text = -120
End With
BiasAdjust = IsDaylightSavingTime()
With Label1
If BiasAdjust Then
.Caption = "(Bias shown is for Daylight Saving Time)"
Else
.Caption = "(Bias shown is for Standard Time)"
End If
End With
End Sub
Private Sub Command1_Click()
'enable the lookup key if
'results returned
Command2.Enabled = GetTimeZoneArray()
End Sub
Private Sub Command2_Click()
Dim cnt As Long
'do a lookup for the Bias entered
With List2
.Clear
For cnt = LBound(tzinfo) To UBound(tzinfo)
If tzinfo(cnt).Bias = Text1.Text Then
.AddItem tzinfo(cnt).TimeZoneName
Debug.Print tzinfo(cnt).TimeZoneName
End If
Next
End With
End Sub
Private Sub List1_Click()
Dim pos As Long
'on a list click, show the Bias in the
'textbox to make lookups easier
If List1.ListIndex > -1 Then
pos = InStr(List1.List(List1.ListIndex), vbTab)
Text1.Text = Left$(List1.List(List1.ListIndex), pos - 1)
End If
End Sub
Private Function GetTimeZoneArray() As Boolean
Dim success As Long
Dim dwIndex As Long
Dim cbName As Long
Dim hKey As Long
Dim sName As String
Dim dwSubKeys As Long
Dim dwMaxSubKeyLen As Long
Dim ft As FILETIME
'Win9x and WinNT have a slightly
'different registry structure.
'Determine the operating system and
'set a module variable to the
'correct key.
'assume OS is win9x
sTzKey = SKEY_9X
'see if OS is NT, and if so,
'use assign the correct key
If IsWinNTPlus Then sTzKey = SKEY_NT
'BiasAdjust is used when calculating the
'bias values retrieved from the registry.
'If True, the reg value retrieved represents
'the location's bias with the bias for
'daylight saving time added. If false, the
'location's bias is returned with the
'standard bias adjustment applied (this
'is usually 0). Doing this allows us to
'use the bias returned from a TIME_OF_DAY_INFO
'call as the correct lookup value dependant
'on whether the world is currently on
'daylight saving time or not. For those
'countries not recognizing daylight saving
'time, the registry daylight bias will be 0,
'therefore proper lookup will not be affected.
'Not considered (nor can such be coded) are those
'special areas within a given country that do
'not recognize daylight saving time, even
'when the rest of the country does (like
'Saskatchewan in Canada).
BiasAdjust = IsDaylightSavingTime()
'open the timezone registry key
hKey = OpenRegKey(HKEY_LOCAL_MACHINE, sTzKey)
If hKey <> 0 Then
'query registry for the number of
'entries under that key
If RegQueryInfoKey(hKey, _
0&, _
0&, _
0, _
dwSubKeys, _
dwMaxSubKeyLen&, _
0&, _
0&, _
0&, _
0&, _
0&, _
ft) = ERROR_SUCCESS Then
'create a UDT array for the time zone info
ReDim tzinfo(0 To dwSubKeys - 1) As TZ_LOOKUP_DATA
dwIndex = 0
cbName = 32
Do
'pad a string for the returned value
sName = Space$(cbName)
success = RegEnumKey(hKey, dwIndex, sName, cbName)
If success = ERROR_SUCCESS Then
'add the data to the appropriate
'tzinfo UDT array members
With tzinfo(dwIndex)
.TimeZoneName = TrimNull(sName)
.Bias = GetTZBiasByName(.TimeZoneName)
.IsDST = BiasAdjust
'for demo purposes only, the data
'is also added to a list
List1.AddItem .Bias & vbTab & .TimeZoneName
End With
End If
'increment the loop...
dwIndex = dwIndex + 1
'...and continue while the reg
'call returns success.
Loop While success = ERROR_SUCCESS
'clean up
RegCloseKey hKey
'return success if, well, successful
GetTimeZoneArray = dwIndex > 0
End If 'If RegQueryInfoKey
Else
'could not open reg key
GetTimeZoneArray = False
End If 'If hKey
End Function
Private Function IsDaylightSavingTime() As Boolean
Dim tzi As TIME_ZONE_INFORMATION
IsDaylightSavingTime = GetTimeZoneInformation(tzi) = TIME_ZONE_ID_DAYLIGHT
End Function
Private Function GetTZBiasByName(sTimeZone As String) As Long
Dim rtzi As REG_TIME_ZONE_INFORMATION
Dim hKey As Long
'open the passed time zone key
hKey = OpenRegKey(HKEY_LOCAL_MACHINE, sTzKey & "\" & sTimeZone)
If hKey <> 0 Then
'obtain the data from the TZI member
If RegQueryValueEx(hKey, _
"TZI", _
0&, _
ByVal 0&, _
rtzi, _
Len(rtzi)) = ERROR_SUCCESS Then
'tweak the Bias when in Daylight Saving time
If BiasAdjust Then
GetTZBiasByName = (rtzi.Bias + rtzi.DaylightBias)
Else
GetTZBiasByName = (rtzi.Bias + rtzi.StandardBias) 'StandardBias is usually 0
End If
End If
RegCloseKey hKey
End If
End Function
Private Function TrimNull(startstr As String) As String
TrimNull = Left$(startstr, lstrlenW(StrPtr(startstr)))
End Function
Private Function OpenRegKey(ByVal hKey As Long, _
ByVal lpSubKey As String) As Long
Dim hSubKey As Long
If RegOpenKeyEx(hKey, _
lpSubKey, _
0, _
KEY_READ, _
hSubKey) = ERROR_SUCCESS Then
OpenRegKey = hSubKey
End If
End Function
Private Function IsWinNTPlus() As Boolean
'returns True if running WinNT or better
#If Win32 Then
Dim OSV As OSVERSIONINFO
OSV.OSVSize = Len(OSV)
If GetVersionEx(OSV) = 1 Then
IsWinNTPlus = (OSV.PlatformID = VER_PLATFORM_WIN32_NT)
End If
#End If
End Function |
|
|
|
|
|
|||||
|
|||||
|
|
|||||
|
Copyright ©1996-2011 VBnet and Randy Birch. All Rights Reserved. |
![]() |