Link to home
Start Free TrialLog in
Avatar of AlHal2
AlHal2Flag for United Kingdom of Great Britain and Northern Ireland

asked on

Populating an array of locale_time_info

I have an array set up like this.
 Public LocTZI() As LOCALE_TIME_ZONE_INFORMATION

I want to loop through a collection like this and populate the array.

Dim timeZones As ReadOnlyCollection(Of TimeZoneInfo) = TimeZoneInfo.GetSystemTimeZones()

For Each
...
next

Locale_Time_Zone_Information is a structure as follows:

    Public Structure LOCALE_TIME_ZONE_INFORMATION
        Dim Bias As Integer
        Dim StandardBias As Integer
        Dim DaylightBias As Integer
        Dim StandardDate As SYSTEMTIME
        Dim DaylightDate As SYSTEMTIME
        Dim DisplayName As String
        Dim StandardName As String
        Dim DaylightName As String
        Dim MapID As String
    End Structure
How do I populate the arrary with this collection?  I'm using visual studio 2010 for a windows (rather than web) program.
The program will run on a server with Windows 2012 R2.  I'm developing it on a machine with Windows 7.
Avatar of it_saige
it_saige
Flag of United States of America image

You mean something like this???

Code -
Imports System.Runtime.InteropServices

Module Module1
	Sub Main()
		Dim LocalTZI = (From tz In TimeZoneInfo.GetSystemTimeZones() Select CType(tz, LOCAL_TIME_ZONE_INFORMATION)).ToArray()
		For Each localTZ In LocalTZI
			Console.WriteLine(localTZ.DisplayName)
		Next
		Console.ReadLine()
	End Sub
End Module

<StructLayout(LayoutKind.Sequential)> _
Structure FILETIME
	Public dwLowDateTime As UInteger
	Public dwHighDateTime As UInteger

	Public ReadOnly Property Value() As ULong
		Get
			Return CType(dwHighDateTime << 32, ULong) + dwLowDateTime
		End Get
	End Property
End Structure

<StructLayout(LayoutKind.Sequential)> _
Structure SYSTEMTIME
	Public wYear As UInt16
	Public wMonth As UInt16
	Public wDayOfWeek As UInt16
	Public wDay As UInt16
	Public wHour As UInt16
	Public wMinute As UInt16
	Public wSecond As UInt16
	Public wMilliseconds As UInt16

	Public Shared Widening Operator CType(ByVal st As SYSTEMTIME) As DateTime
		Dim ft As New FILETIME()
		Dim dt As DateTime = Nothing
		NativeMethods.SystemTimeToFileTime(st, ft)
		dt = New DateTime((CLng(ft.dwHighDateTime) << 32) Or CUInt(ft.dwLowDateTime))
		Return dt
	End Operator

	Public Shared Narrowing Operator CType(ByVal dt As DateTime) As SYSTEMTIME
		Dim ft As New FILETIME() With {.dwHighDateTime = CInt(dt.Ticks >> 32), .dwLowDateTime = CInt(dt.Ticks And &HFFFFFFFFL)}
		Dim st As SYSTEMTIME = Nothing
		NativeMethods.FileTimeToSystemTime(ft, st)
		Return st
	End Operator
End Structure

Structure LOCAL_TIME_ZONE_INFORMATION
	Public Property Bias() As Integer
	Public Property StandardBias() As Integer
	Public Property DaylightBias() As Integer
	Public Property StandardDate() As SYSTEMTIME
	Public Property DaylightDate() As SYSTEMTIME
	Public Property DisplayName() As String
	Public Property StandardName() As String
	Public Property DaylightName() As String
	Public Property MapID() As String

	Public Shared Narrowing Operator CType(ByVal tzi As TimeZoneInfo) As LOCAL_TIME_ZONE_INFORMATION
		Dim rules = tzi.GetAdjustmentRules()
		Dim rule As TimeZoneInfo.AdjustmentRule = Nothing
		If rules.Length > 0 Then rule = rules.SingleOrDefault(Function(ar) ar.DateStart < DateTime.Now AndAlso DateTime.Now <= ar.DateEnd)

		Dim ltzi As New LOCAL_TIME_ZONE_INFORMATION() With {
			.Bias = -tzi.BaseUtcOffset.TotalMinutes,
			.DaylightBias = If(rule Is Nothing, -60, -rule.DaylightDelta.TotalMinutes),
			.DaylightDate = New SYSTEMTIME() With {.wDay = 0, .wDayOfWeek = 0, .wHour = 0, .wMilliseconds = 0, .wMinute = 0, .wMonth = 0, .wSecond = 0, .wYear = 0},
			.DaylightName = tzi.DaylightName,
			.DisplayName = tzi.DisplayName,
			.MapID = "",
			.StandardBias = -tzi.BaseUtcOffset.TotalMinutes,
			.StandardDate = New SYSTEMTIME() With {.wDay = 0, .wDayOfWeek = 0, .wHour = 0, .wMilliseconds = 0, .wMinute = 0, .wMonth = 0, .wSecond = 0, .wYear = 0},
			.StandardName = tzi.StandardName
			}

		With ltzi
			If rule IsNot Nothing Then
				Dim dTime = rule.DaylightTransitionStart
				Dim sTime = rule.DaylightTransitionEnd

				.DaylightDate = New SYSTEMTIME() With {
					.wDay = If(dTime.IsFixedDateRule, dTime.Day, dTime.Week),
					.wDayOfWeek = If(dTime.IsFixedDateRule, -1, dTime.DayOfWeek),
					.wHour = dTime.TimeOfDay.Hour,
					.wMonth = dTime.Month
					}

				.StandardDate = New SYSTEMTIME() With {
					.wDay = If(sTime.IsFixedDateRule, sTime.Day, sTime.Week),
					.wDayOfWeek = If(sTime.IsFixedDateRule, -1, sTime.DayOfWeek),
					.wHour = sTime.TimeOfDay.Hour,
					.wMonth = sTime.Month
					}
			End If
		End With
		Return ltzi
	End Operator
End Structure

Module NativeMethods
	<DllImport("kernel32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
	Public Function FileTimeToSystemTime(<[In]()> ByRef lpFileTime As FILETIME, <Out()> ByRef lpSystemTime As SYSTEMTIME) As Boolean
	End Function

	<DllImport("kernel32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
	Public Function SystemTimeToFileTime(<[In]()> ByRef lpSystemTime As SYSTEMTIME, <Out()> ByRef lpFileTime As FILETIME) As Boolean
	End Function
End Module

Open in new window

Produces the following output -User generated image
-saige-
@it_sage. Nice job! Though it doesn't reflect to current solution, you need add/subtract 1600 years when convering filetime to/from DateTime.
dt = New DateTime(ft.Value, DateTimeKind.Utc).AddYears(1600)

Open in new window

Avatar of AlHal2

ASKER

I'm getting an error message

'localTZ' is not declared. It may be inaccessible due to its protection level.
Avatar of AlHal2

ASKER

This line also gives a warning message.

Dim LocalTZI = (From tz In TimeZoneInfo.GetSystemTimeZones() Select CType(tz, LOCAL_TIME_ZONE_INFORMATION)).ToArray()
Variable defined without an AS clause. Type of Object assumed
I corrected the first error by amending as follows:
For Each localTZ As TimeZoneInfo In LocalTZI

When I run the program I get another message "Public member 'singleordefault' on type AdjustmentRule() not found"
ASKER CERTIFIED SOLUTION
Avatar of it_saige
it_saige
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
And reverse CType:
Dim ticks As Long = dt.AddYears(-1600).Ticks
Dim ft As New FILETIME() With {.dwHighDateTime = CInt(ticks >> 32), 
                               .dwLowDateTime = CInt(ticks And &HFFFFFFFFL)}

Open in new window

OK. First of all - is there a reason to convert TimeZoneInfo to structure? TimeZoneInfo contains [almost] all info on TimeZone. There are some difference though:
1. Bias and StandardBias (which is usually 0) are summarized to BaseUtcOffset
2. Values of bias are negated, because .Net class use LocalTime = UTC + Bias logic while API use UTC = LocalTime + Bias
3. Structure contains only last (according windows update) DayLight rule while .Net class contains all rules for previous period.
Actually your structure is 'syntetic' structure - it's not MSDN defilned. I was surprised it's equal to this sample i wrote (or my!) 16 years ago. Novadays using Net classes make things much easy. Also note that MapId is not supported by Vista and later versions. If you still need populate structure, IMHO better use registry directly, without TimeZoneInfo class:
Imports System.Runtime.InteropServices
Imports Microsoft.Win32

Public Class Registry_TZI

    <StructLayout(LayoutKind.Sequential)> _
    Public Structure SYSTEMTIME
        Public wYear As UInt16
        Public wMonth As UInt16
        Public wDayOfWeek As UInt16
        Public wDay As UInt16
        Public wHour As UInt16
        Public wMinute As UInt16
        Public wSecond As UInt16
        Public wMilliseconds As UInt16
    End Structure
    '44 bytes under TZI registry value
    Structure REGISTRY_TIME_ZONE_INFORMATION
        Public Property Bias As Integer
        Public Property StandardBias As Integer
        Public Property DaylightBias As Integer
        Public Property StandardDate As SYSTEMTIME
        Public Property DaylightDate As SYSTEMTIME
    End Structure
   'Structure to use with GetTimeZoneInformation API
    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)>
    Public Structure TIME_ZONE_INFORMATION
        <MarshalAs(UnmanagedType.I4)>
        Public Bias As Integer
        <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=32)>
        Public StandardName As String
        Public StandardDate As SYSTEMTIME
        <MarshalAs(UnmanagedType.I4)>
        Public StandardBias As Integer
        <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=32)>
        Public DaylightName As String
        Public DaylightDate As SYSTEMTIME
        <MarshalAs(UnmanagedType.I4)>
        Public DaylightBias As Integer
    End Structure
    Public Structure LOCALE_TIME_ZONE_INFORMATION
        Public Property Bias As Integer
        Public Property StandardBias As Integer
        Public Property DaylightBias As Integer
        Public Property StandardDate As SYSTEMTIME
        Public Property DaylightDate As SYSTEMTIME
        Public Property DisplayName As String
        Public Property StandardName As String
        Public Property DaylightName As String
        Public Property MapID As String
    End Structure

    <DllImport("kernel32.dll")> _
    Private Shared Function GetTimeZoneInformation(ByRef lpTimeZoneInformation As TIME_ZONE_INFORMATION) As Int32
    End Function

    Public Shared Function EnumLTZI() As List(Of LOCALE_TIME_ZONE_INFORMATION)
        Dim listTZ As New List(Of LOCALE_TIME_ZONE_INFORMATION)
        Using key As RegistryKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones")
            Dim zoneNames As String() = key.GetSubKeyNames()
            For Each zoneName In zoneNames
                listTZ.Add(GetLTZI(key, zoneName))
            Next
        End Using
        Return listTZ
    End Function
    Public Shared Function CurrentTimeZone() As LOCALE_TIME_ZONE_INFORMATION
        If Environment.OSVersion.Version.Major >= 6 Then 'Vista and later
            Dim ltzi As New LOCALE_TIME_ZONE_INFORMATION
            Using key As RegistryKey = Registry.LocalMachine.OpenSubKey("SYSTEM\CurrentControlSet\Control\TimeZoneInformation")
                Return GetLTZI(key.GetValue("TimeZoneKeyName").ToString.TrimEnd(vbNullChar))
            End Using
        Else
            Dim tzi As New TIME_ZONE_INFORMATION
            Dim ret As Integer = GetTimeZoneInformation(tzi)
            If ret = 0 Then
                For Each ltzi In EnumLTZI()
                    If ltzi.StandardName = tzi.StandardName AndAlso
                       ltzi.StandardBias = tzi.StandardBias AndAlso
                       ltzi.StandardDate.Equals(tzi.StandardDate) Then Return ltzi
                Next
            End If
        End If
        Return Nothing
    End Function
    Public Shared Function GetLTZI(zoneName As String) As LOCALE_TIME_ZONE_INFORMATION
        Using key As RegistryKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones")
            Return GetLTZI(key, zoneName)
        End Using
    End Function

    Private Shared Function GetLTZI(tziKey As RegistryKey, zoneName As String) As LOCALE_TIME_ZONE_INFORMATION
        Using subkey As RegistryKey = tziKey.OpenSubKey(zoneName)
            If subkey Is Nothing Then Return Nothing
            Dim rtzi As REGISTRY_TIME_ZONE_INFORMATION = GetBinaryTZI(CType(subkey.GetValue("TZI"), Byte()))
            Dim mapId As Object = subkey.GetValue("MapId") 'Vista and later doesn't support MapId
            Return New LOCALE_TIME_ZONE_INFORMATION With {.Bias = rtzi.Bias,
                                                         .StandardBias = rtzi.StandardBias,
                                                         .DaylightBias = rtzi.DaylightBias, 
                                                         .StandardDate = rtzi.StandardDate,
                                                         .DaylightDate = rtzi.DaylightDate,
                                                         .DisplayName = subkey.GetValue("Display").ToString,
                                                         .StandardName = subkey.GetValue("Std").ToString,
                                                         .DaylightName = subkey.GetValue("Dlt").ToString,
                                                         .MapID = If(mapId Is Nothing, "", mapId.ToString)}
        End Using
    End Function
    Private Shared Function GetBinaryTZI(bytes As Byte()) As REGISTRY_TIME_ZONE_INFORMATION
        Dim rtzi As New REGISTRY_TIME_ZONE_INFORMATION
        If Marshal.SizeOf(rtzi) = bytes.Length Then
            Dim h As GCHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned)
            rtzi = Marshal.PtrToStructure(h.AddrOfPinnedObject, rtzi.GetType)
            h.Free()
        End If
        Return rtzi
    End Function

End Class

Open in new window

Using:
    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
        Dim ltzi = Registry_TZI.CurrentTimeZone
        MsgBox("Current time zone is: " & ltzi.DisplayName)
        For Each ltzi In Registry_TZI.EnumLTZI
            Debug.Print(ltzi.DisplayName)
        Next
    End Sub

Open in new window

Avatar of AlHal2

ASKER

Thanks.