Daylight Saving Time Error

It’s DST here in the US and I couldn’t be happier. I don’t care if my drive to work is pitch black, but the drive home? That’s another story.

One thing I learned since the clocks sprung forward is that the GetTimeZoneInformation API doesn’t work the way I thought. The TIME_ZONE_INFORMATION return type has a Bias property. Bias tells you how many minutes you are away from GMT. Or so I thought. It actually tells you how many minutes you are from GMT in standard time. The TIME_ZONE return value tells you if it’s daylight saving time or standard time. So you have to take both into account to get the correct time.

Here’s the API declaration

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(0 To 31) As Integer
StandardDate As SYSTEMTIME
StandardBias As Long
DaylightName(0 To 31) As Integer
DaylightDate As SYSTEMTIME
DaylightBias As Long
End Type

''''''''''''''''''''''''''''''''''''''''''''''
' These give symbolic names to the time zone
' values returned by GetTimeZoneInformation .
''''''''''''''''''''''''''''''''''''''''''''''

Private Enum TIME_ZONE
TIME_ZONE_ID_INVALID = 0 ' Cannot determine DST
TIME_ZONE_STANDARD = 1 ' Standard Time, not Daylight
TIME_ZONE_DAYLIGHT = 2 ' Daylight Time, not Standard
End Enum

Private Declare Function GetTimeZoneInformation Lib "kernel32" _
(lpTimeZoneInformation As TIME_ZONE_INFORMATION) As Long

Private Declare Sub GetSystemTime Lib "kernel32" _
(lpSystemTime As SYSTEMTIME)

And the updated procedure:

Public Function ConvertTimeToLocal(ByVal dtTime As Date, ByVal sZone As String) As Date

Dim tzi As TIME_ZONE_INFORMATION
Dim tz As TIME_ZONE
Dim lGmtOff As Long

tz = GetTimeZoneInformation(tzi)

Select Case UCase(sZone)
Case "EDT"
lGmtOff = -4
Case "EST", "CDT"
lGmtOff = -5
Case "CST", "MDT"
lGmtOff = -6
Case "MST", "PDT"
lGmtOff = -7
Case "PST"
lGmtOff = -8
Case vbNullString
lGmtOff = -tzi.Bias / 60
End Select

If tz = TIME_ZONE_DAYLIGHT Then lGmtOff = lGmtOff - 1

ConvertTimeToLocal = dtTime - (TimeSerial(0, tzi.Bias, 0) + TimeSerial(lGmtOff, 0, 0))

End Function

I also added a UCase around the zone because it’s just stupid not to have that. Enjoy saving the daylight, but remember you’ll owe it back this fall.

Update:

That’s why we write tests people.

Public Function ConvertTimeToLocal(ByVal dtTime As Date, ByVal sZone As String) As Date

Dim tzi As TIME_ZONE_INFORMATION
Dim tz As TIME_ZONE
Dim lGmtOff As Long
Dim lBias As Long

tz = GetTimeZoneInformation(tzi)

lBias = tzi.Bias
If tz = TIME_ZONE_DAYLIGHT Then lBias = lBias - 60

Select Case UCase(sZone)
Case "EDT"
lGmtOff = -4
Case "EST", "CDT"
lGmtOff = -5
Case "CST", "MDT"
lGmtOff = -6
Case "MST", "PDT"
lGmtOff = -7
Case "PST"
lGmtOff = -8
Case vbNullString
lGmtOff = -tzi.Bias / 60
If tz = TIME_ZONE_DAYLIGHT Then lGmtOff = lGmtOff + 1
End Select

ConvertTimeToLocal = dtTime - TimeSerial(lGmtOff, 0, 0) - TimeSerial(0, lBias, 0)

End Function

Public Sub Test_ConvertTimeToLocal()

Dim dtTestTime As Date

dtTestTime = TimeSerial(9, 46, 13)

Debug.Assert ConvertTimeToLocal(dtTestTime, vbNullString) - TimeSerial(9, 46, 13) < TimeSerial(0, 0, 1) Debug.Assert ConvertTimeToLocal(dtTestTime, "CDT") - TimeSerial(9, 46, 13) < TimeSerial(0, 0, 1) Debug.Assert ConvertTimeToLocal(dtTestTime, "EST") - TimeSerial(9, 46, 13) < TimeSerial(0, 0, 1) Debug.Assert ConvertTimeToLocal(dtTestTime, "EDT") - TimeSerial(8, 46, 13) < TimeSerial(0, 0, 1) Debug.Assert ConvertTimeToLocal(dtTestTime, "CST") - TimeSerial(10, 46, 13) < TimeSerial(0, 0, 1) Debug.Assert ConvertTimeToLocal(dtTestTime, "MDT") - TimeSerial(10, 46, 13) < TimeSerial(0, 0, 1) Debug.Assert ConvertTimeToLocal(dtTestTime, "MST") - TimeSerial(11, 46, 13) < TimeSerial(0, 0, 1) Debug.Assert ConvertTimeToLocal(dtTestTime, "PDT") - TimeSerial(11, 46, 13) < TimeSerial(0, 0, 1) Debug.Assert ConvertTimeToLocal(dtTestTime, "PST") - TimeSerial(12, 46, 13) < TimeSerial(0, 0, 1) End Sub

6 thoughts on “Daylight Saving Time Error

  1. Is there a way to determine if a certain date was in daylight savings or not? I have a need to look at a historical date and make that determination.

  2. Genius!

    Thanks so much! I learned a lot with this post and the hint about GetTimeZoneInformationForYear!

  3. I was wondering if Eric got the GetTimeZoneInfomationForYear API to work. I have been trying but I don’t understand the Windows API calls well enough.

Leave a Reply

Your email address will not be published. Required fields are marked *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax