Attribute VB_Name = "ex_Time" ' ======== Time conversion ======== ' Shared module version: 20210630 ' Tested in: ' Depends on: ' Required reference: Option Private Module Option Explicit '' ' VBA-UTC v1.0.6 ' (c) Tim Hall - https://github.com/VBA-tools/VBA-UtcConverter ' UTC/ISO 8601 Converter for VBA ' @license MIT (http://www.opensource.org/licenses/mit-license.php) '' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ' Private Declare PtrSafe Function UTC_GetTimeZoneInfo Lib "kernel32" Alias "GetTimeZoneInformation" _ (utc_lpTimeZoneInformation As TTimeZone) As Long Private Declare PtrSafe Function UTC_SystemTimeToLocalTime Lib "kernel32" Alias "SystemTimeToTzSpecificLocalTime" _ (utc_lpTimeZoneInformation As TTimeZone, utc_lpUniversalTime As TSystemTime, utc_lpLocalTime As TSystemTime) As Long Private Declare PtrSafe Function UTC_LocalTimeToSystemTime Lib "kernel32" Alias "TzSpecificLocalTimeToSystemTime" _ (utc_lpTimeZoneInformation As TTimeZone, utc_lpLocalTime As TSystemTime, utc_lpUniversalTime As TSystemTime) As Long Private Type TSystemTime utc_wYear As Integer utc_wMonth As Integer utc_wDayOfWeek As Integer utc_wDay As Integer utc_wHour As Integer utc_wMinute As Integer utc_wSecond As Integer utc_wMilliseconds As Integer End Type Private Type TTimeZone utc_Bias As Long utc_StandardName(0 To 31) As Integer utc_StandardDate As TSystemTime utc_StandardBias As Long utc_DaylightName(0 To 31) As Integer utc_DaylightDate As TSystemTime utc_DaylightBias As Long End Type Private Const ERR_UTC_PARSE_FAIL = vbObjectError + 1025 Private Const ERR_UTC_CONVERSION_FAIL = vbObjectError + 1026 Private Const ERR_ISO_PARSE_FAIL = vbObjectError + 1027 Private Const ERR_ISO_CONVERSION_FAIL = vbObjectError + 1028 ' Parse UTC date to local date Public Function ParseUtc(utc_UtcDate As Date) As Date On Error GoTo ON_ERROR Dim utc_TimeZoneInfo As TTimeZone Dim utc_LocalDate As TSystemTime Call UTC_GetTimeZoneInfo(utc_TimeZoneInfo) Call UTC_SystemTimeToLocalTime(utc_TimeZoneInfo, UTC_DateToSystemTime(utc_UtcDate), utc_LocalDate) ParseUtc = UTC_SystemTimeToDate(utc_LocalDate) Exit Function ON_ERROR: Call Err.Raise(ERR_UTC_PARSE_FAIL, "UtcConverter.ParseUtc", "UTC parsing error: " & Err.Number & " - " & Err.Description) End Function ' Convert local date to UTC date Public Function ConvertToUtc(utc_LocalDate As Date) As Date On Error GoTo ON_ERROR Dim utc_TimeZoneInfo As TTimeZone Dim utc_UtcDate As TSystemTime UTC_GetTimeZoneInfo utc_TimeZoneInfo UTC_LocalTimeToSystemTime utc_TimeZoneInfo, UTC_DateToSystemTime(utc_LocalDate), utc_UtcDate ConvertToUtc = UTC_SystemTimeToDate(utc_UtcDate) Exit Function ON_ERROR: Call Err.Raise(ERR_UTC_PARSE_FAIL, "UtcConverter.ConvertToUtc", "UTC conversion error: " & Err.Number & " - " & Err.Description) End Function ' Parse ISO 8601 date string to local date Public Function ParseIso(sISOTime$) As Date On Error GoTo ON_ERROR Dim utc_Parts() As String Dim utc_DateParts() As String Dim utc_TimeParts() As String Dim utc_OffsetIndex& Dim utc_HasOffset As Boolean Dim utc_NegativeOffset As Boolean Dim utc_OffsetParts() As String Dim utc_Offset As Date utc_Parts = VBA.Split(sISOTime, "T") utc_DateParts = VBA.Split(utc_Parts(0), "-") ParseIso = VBA.DateSerial(VBA.CInt(utc_DateParts(0)), VBA.CInt(utc_DateParts(1)), VBA.CInt(utc_DateParts(2))) If UBound(utc_Parts) > 0 Then If VBA.InStr(utc_Parts(1), "Z") Then utc_TimeParts = VBA.Split(VBA.Replace(utc_Parts(1), "Z", ""), ":") Else utc_OffsetIndex = VBA.InStr(1, utc_Parts(1), "+") If utc_OffsetIndex = 0 Then utc_NegativeOffset = True utc_OffsetIndex = VBA.InStr(1, utc_Parts(1), "-") End If If utc_OffsetIndex > 0 Then utc_HasOffset = True utc_TimeParts = VBA.Split(VBA.Left$(utc_Parts(1), utc_OffsetIndex - 1), ":") utc_OffsetParts = VBA.Split(VBA.Right$(utc_Parts(1), Len(utc_Parts(1)) - utc_OffsetIndex), ":") Select Case UBound(utc_OffsetParts) Case 0 utc_Offset = TimeSerial(VBA.CInt(utc_OffsetParts(0)), 0, 0) Case 1 utc_Offset = TimeSerial(VBA.CInt(utc_OffsetParts(0)), VBA.CInt(utc_OffsetParts(1)), 0) Case 2 ' VBA.Val does not use regional settings, use for seconds to avoid decimal/comma issues utc_Offset = TimeSerial(VBA.CInt(utc_OffsetParts(0)), VBA.CInt(utc_OffsetParts(1)), Int(VBA.Val(utc_OffsetParts(2)))) End Select If utc_NegativeOffset Then: utc_Offset = -utc_Offset Else utc_TimeParts = VBA.Split(utc_Parts(1), ":") End If End If Select Case UBound(utc_TimeParts) Case 0 ParseIso = ParseIso + VBA.TimeSerial(VBA.CInt(utc_TimeParts(0)), 0, 0) Case 1 ParseIso = ParseIso + VBA.TimeSerial(VBA.CInt(utc_TimeParts(0)), VBA.CInt(utc_TimeParts(1)), 0) Case 2 ' VBA.Val does not use regional settings, use for seconds to avoid decimal/comma issues ParseIso = ParseIso + VBA.TimeSerial(VBA.CInt(utc_TimeParts(0)), VBA.CInt(utc_TimeParts(1)), Int(VBA.Val(utc_TimeParts(2)))) End Select ParseIso = ParseUtc(ParseIso) If utc_HasOffset Then ParseIso = ParseIso - utc_Offset End If End If Exit Function ON_ERROR: Err.Raise ERR_ISO_PARSE_FAIL, "UtcConverter.ParseIso", "ISO 8601 parsing error for " & sISOTime & ": " & Err.Number & " - " & Err.Description End Function ' Convert local date to ISO 8601 string Public Function ConvertToIso(utc_LocalDate As Date) As String On Error GoTo ON_ERROR ConvertToIso = VBA.Format$(ConvertToUtc(utc_LocalDate), "yyyy-mm-ddTHH:mm:ss.000Z") Exit Function ON_ERROR: Call Err.Raise(ERR_ISO_CONVERSION_FAIL, "UtcConverter.ConvertToIso", "ISO 8601 conversion error: " & Err.Number & " - " & Err.Description) End Function ' ============================================= Private Function UTC_DateToSystemTime(utc_Value As Date) As TSystemTime UTC_DateToSystemTime.utc_wYear = VBA.Year(utc_Value) UTC_DateToSystemTime.utc_wMonth = VBA.Month(utc_Value) UTC_DateToSystemTime.utc_wDay = VBA.Day(utc_Value) UTC_DateToSystemTime.utc_wHour = VBA.Hour(utc_Value) UTC_DateToSystemTime.utc_wMinute = VBA.Minute(utc_Value) UTC_DateToSystemTime.utc_wSecond = VBA.Second(utc_Value) UTC_DateToSystemTime.utc_wMilliseconds = 0 End Function Private Function UTC_SystemTimeToDate(utc_Value As TSystemTime) As Date UTC_SystemTimeToDate = DateSerial(utc_Value.utc_wYear, utc_Value.utc_wMonth, utc_Value.utc_wDay) + _ TimeSerial(utc_Value.utc_wHour, utc_Value.utc_wMinute, utc_Value.utc_wSecond) End Function