I wrote this class two years ago that should solve your problem.
Please let me know if it contains any bug
regards.-
mau
Imports Microsoft.Win32
Imports System.Runtime.InteropServices
Imports System.Globalization
Friend Class CustomTimeZone
Inherits System.TimeZone
' system types
<StructLayout(LayoutKind.Sequential, pack:=2)> Private Structure SYSTEMTIME
Dim wYear As Short
Dim wMonth As Short
Dim wDayOfWeek As Short
Dim wDay As Short
Dim wHour As Short
Dim wMinute As Short
Dim wSecond As Short
Dim wMilliseconds As Short
End Structure
<StructLayout(LayoutKind.Sequential, pack:=2)> Private Structure TZI
Dim Bias As Integer
Dim StandardBias As Integer
Dim DaylightBias As Integer
Dim StandardDate As SYSTEMTIME
Dim DaylightDate As SYSTEMTIME
End Structure
<StructLayout(LayoutKind.Sequential, pack:=2)> Private Structure
TIME_ZONE_INFORMATION
Dim Bias As Integer
<VBFixedString(32)> Dim StandardName As String
Dim StandardDate As SYSTEMTIME
Dim StandardBias As Integer
<VBFixedString(32)> Dim DaylightName As String
Dim DaylightDate As SYSTEMTIME
Dim DaylightBias As Integer
End Structure
' system timezones
Private Shared Zones As TimeZonesCollection
' private vars
Dim _displayName As String
Dim _id As Integer
Dim _timeZoneInfo As TIME_ZONE_INFORMATION
Private Shared _currentZone As CustomTimeZone
Private Sub New(ByVal fromTZI As TZI, ByVal zoneName As String, ByVal
zoneDisplay As String, _
ByVal zoneID As Integer, ByVal zoneDaylightName As String)
_displayName = zoneDisplay
_id = zoneID
_timeZoneInfo.Bias = fromTZI.Bias
_timeZoneInfo.StandardName = zoneName
_timeZoneInfo.StandardDate = fromTZI.StandardDate
_timeZoneInfo.StandardBias = fromTZI.StandardBias
_timeZoneInfo.DaylightName = zoneDaylightName
_timeZoneInfo.DaylightDate = fromTZI.DaylightDate
_timeZoneInfo.DaylightBias = fromTZI.DaylightBias
End Sub
#Region "Private methods"
Private Shared Function CheckWindowsNT() As Boolean
' ritorna true se è windows NT o 2000 o XP
If Environment.OSVersion.Platform = PlatformID.Win32NT Then Return True
End Function
Private Shared Function SystemTimeToDateTime(ByVal S As SYSTEMTIME, ByVal
Year As Integer) As Date
If S.wYear <> 0 Then
' data assoluta
Return New Date(S.wYear, S.wMonth, S.wDay, S.wHour, S.wMinute, S.wSecond,
S.wMilliseconds)
Else
' data relativa, es. ultima domenica di marzo
Dim DT As Date
Dim Diff As Integer
If S.wDay <= 4 Then
Dim FirstMonthDayOfWeek As Integer
DT = New Date(Year, S.wMonth, 1, S.wHour, S.wMinute, S.wSecond,
S.wMilliseconds)
FirstMonthDayOfWeek = DT.DayOfWeek
Diff = S.wDayOfWeek - FirstMonthDayOfWeek
If Diff < 0 Then Diff += 7
Diff = 7 * S.wDay - 1
If Diff > 0 Then DT = DT.AddDays(Diff)
Else
Dim C As New GregorianCalendar
Dim LastDayDayOfWeek As Integer
DT = New Date(Year, S.wMonth, C.GetDaysInMonth(Year, S.wMonth), S.wHour,
S.wMinute, S.wSecond, S.wMilliseconds)
LastDayDayOfWeek = DT.DayOfWeek
Diff = LastDayDayOfWeek - S.wDayOfWeek
If Diff < 0 Then Diff += 7
If Diff > 0 Then DT = DT.AddDays(-Diff)
End If
Return DT
End If
End Function
Private Shared Function CalculateUTCOffset(ByVal time As Date, ByVal
daylightTimes As DaylightTime) As TimeSpan
Dim D1, D2 As Date
Dim T As TimeSpan
If daylightTimes Is Nothing Then Return TimeSpan.Zero
T = daylightTimes.Delta
If T.Ticks > 0 Then
D1 = daylightTimes.Start.AddTicks(daylightTimes.Delta.Ticks)
D2 = daylightTimes.End
Else
D1 = daylightTimes.Start
D2 = daylightTimes.End.AddTicks(-daylightTimes.Delta.Ticks)
End If
If D1 > D2 Then
If Not (time < D2) And Not (time >= D1) Then Return TimeSpan.Zero
Return daylightTimes.Delta
End If
If time > D1 And time < D2 Then Return daylightTimes.Delta
Return TimeSpan.Zero
End Function
#End Region
#Region "Public Properties"
Public Overrides ReadOnly Property DaylightName() As String
Get
If _timeZoneInfo.StandardDate.wMonth = 0 Then Return
_timeZoneInfo.StandardName
Return _timeZoneInfo.DaylightName
End Get
End Property
Public Overrides ReadOnly Property StandardName() As String
Get
Return _timeZoneInfo.StandardName
End Get
End Property
Public Overridable ReadOnly Property DisplayName() As String
Get
Return _displayName
End Get
End Property
Public ReadOnly Property ID() As Integer
Get
Return _id
End Get
End Property
#End Region
#Region "Public Shared Members"
Public Shared ReadOnly Property GMT() As CustomTimeZone
Get
Return EnumTimeZones.FindZoneID(85)
End Get
End Property
Public Shared ReadOnly Property PST() As CustomTimeZone
Get
Return EnumTimeZones.FindZoneID(4)
End Get
End Property
Public Shared ReadOnly Property EST() As CustomTimeZone
Get
Return EnumTimeZones.FindZoneID(35)
End Get
End Property
Public Shared Function EnumTimeZones() As TimeZonesCollection
' Registry Constants
Const SKEY_NT = "SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones"
Const SKEY_9X = "SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones"
If Zones Is Nothing Then
Dim SubKey As String
Dim regKey, regTimeKey As RegistryKey
Dim I As Integer
Dim SubKeys As String()
Dim objtzi As TZI
Dim Bytes() As Byte
SyncLock GetType(CustomTimeZone)
If Zones Is Nothing Then ' previene una race-condition
If CheckWindowsNT() Then SubKey = SKEY_NT Else SubKey = SKEY_9X
regKey = Registry.LocalMachine.OpenSubKey(SubKey)
Try
SubKeys = regKey.GetSubKeyNames
Dim tziType As Type = GetType(TZI)
Dim tziSize As Integer = Marshal.SizeOf(tziType)
Dim sZoneName, sZoneDisplay, sDaylightName As String
Dim Index As Integer
Zones = New TimeZonesCollection
For I = 0 To SubKeys.Length - 1
regTimeKey = regKey.OpenSubKey(SubKeys(I))
Bytes = regTimeKey.GetValue("TZI")
sZoneName = regTimeKey.GetValue("Std")
sZoneDisplay = regTimeKey.GetValue("Display")
sDaylightName = regTimeKey.GetValue("Dlt")
Index = regTimeKey.GetValue("Index")
regTimeKey.Close()
If Not (Bytes Is Nothing OrElse Bytes.Length <> tziSize) Then
Dim handle As GCHandle
Dim Buffer As IntPtr
handle = GCHandle.Alloc(Bytes, GCHandleType.Pinned)
Buffer = handle.AddrOfPinnedObject
objtzi = CType(Marshal.PtrToStructure(Buffer, tziType), TZI)
handle.Free()
Zones.Add(New CustomTimeZone(objtzi, sZoneName, sZoneDisplay, Index,
sDaylightName))
End If
Next
regTimeKey = Nothing
Finally
regKey.Close()
If Not (regTimeKey Is Nothing) Then regTimeKey.Close()
End Try
End If
End SyncLock
End If
Return Zones
End Function
Public Shared Function ConvertDateTime(ByVal localDate As Date, ByVal
sourceZone As TimeZone, ByVal targetZone As TimeZone) As Date
Dim utcDate As Date
utcDate = sourceZone.ToUniversalTime(localDate)
Return targetZone.ToLocalTime(utcDate)
End Function
Public Shared Shadows Function CurrentTimeZone() As CustomTimeZone
If _currentZone Is Nothing Then
Dim I As Integer
Dim currentZone As TimeZone
SyncLock GetType(CustomTimeZone)
If _currentZone Is Nothing Then
currentZone = TimeZone.CurrentTimeZone
For I = 0 To EnumTimeZones.Count
If Zones.Item(I).StandardName = currentZone.StandardName Then
_currentZone = Zones(I)
Exit For
End If
Next
End If
End SyncLock
End If
Return _currentZone
End Function
#End Region
#Region "Public Methods"
Public Overrides Function GetHashCode() As Integer
Return _id
End Function
Public Overrides Function ToString() As String
Return StandardName
End Function
Public Overrides Function GetDaylightChanges(ByVal year As Integer) As
System.Globalization.DaylightTime
Dim daylightTime As DaylightTime
If year < 1 OrElse year > 9999 Then Throw New
ArgumentOutOfRangeException("Year must be beetween 1 and 9999!")
If _timeZoneInfo.StandardDate.wMonth = 0 Then
' l'ora legale non è usata
Return New DaylightTime(Date.MinValue, Date.MinValue, TimeSpan.Zero)
Else
Dim startDaylight, endDaylight As Date
Dim DurataOra As TimeSpan
' calcola l'inizio e la fine dell'ora legale
startDaylight = SystemTimeToDateTime(_timeZoneInfo.DaylightDate, year)
endDaylight = SystemTimeToDateTime(_timeZoneInfo.StandardDate, year)
DurataOra = New TimeSpan(-_timeZoneInfo.DaylightBias *
TimeSpan.TicksPerMinute)
Return New DaylightTime(startDaylight, endDaylight, DurataOra)
End If
End Function
Public Overrides Function GetUtcOffset(ByVal time As Date) As
System.TimeSpan
Dim T As TimeSpan
T = CalculateUTCOffset(time, GetDaylightChanges(time.Year))
Return New TimeSpan(T.Ticks - _timeZoneInfo.Bias * TimeSpan.TicksPerMinute)
End Function
#End Region
End Class
Friend Class TimeZonesCollection
Implements ICollection
Dim _Zones As SortedList
Friend Sub New()
_Zones = New SortedList(80)
End Sub
Friend Sub Add(ByVal zone As CustomTimeZone)
_Zones.Add(zone.ID, zone)
End Sub
#Region "Public Properties"
Public ReadOnly Property Count() As Integer Implements
System.Collections.ICollection.Count
Get
Return _Zones.Count
End Get
End Property
Public ReadOnly Property IsSynchronized() As Boolean Implements
System.Collections.ICollection.IsSynchronized
Get
Return (_Zones.IsSynchronized)
End Get
End Property
Public ReadOnly Property SyncRoot() As Object Implements
System.Collections.ICollection.SyncRoot
Get
SyncLock GetType(TimeZonesCollection)
Return Me
End SyncLock
End Get
End Property
Default Public ReadOnly Property Item(ByVal index As Integer) As
CustomTimeZone
Get
Return CType(_Zones.GetByIndex(index), CustomTimeZone)
End Get
End Property
#End Region
#Region "Public Methods"
Public Sub CopyTo(ByVal array As System.Array, ByVal index As Integer)
Implements System.Collections.ICollection.CopyTo
_Zones.CopyTo(array, index)
End Sub
Public Function GetEnumerator() As System.Collections.IEnumerator Implements
System.Collections.IEnumerable.GetEnumerator
Return _Zones.GetEnumerator
End Function
Public Function FindZoneID(ByVal id As Integer) As CustomTimeZone
Return _Zones.Item(id)
End Function
#End Region
End Class