2011-02-04 36 views
44

解析ISO8601的日期/時間(包括時區)我需要解析的ISO8601日期/時間格式與在Excel/VBA包含的時區(從外部源),以正常的Excel日期。據我所知,Excel的XP(這是我們正在使用的)沒有一個例程是內置的,所以我想我在尋找一個自定義的VBA函數解析。在Excel

ISO8601日期時間看起來像這些之一:

2011-01-01 
2011-01-01T12:00:00Z 
2011-01-01T12:00:00+05:00 
2011-01-01T12:00:00-05:00 
2011-01-01T12:00:00.05381+05:00 

回答

35

很多谷歌搜索不轉了什麼,所以我寫我自己的程序。在此處張貼以供將來參考:

Option Explicit 

'--------------------------------------------------------------------- 
' Declarations must be at the top -- see below 
'--------------------------------------------------------------------- 
Public Declare Function SystemTimeToFileTime Lib _ 
    "kernel32" (lpSystemTime As SYSTEMTIME, _ 
    lpFileTime As FILETIME) As Long 

Public Declare Function FileTimeToLocalFileTime Lib _ 
    "kernel32" (lpLocalFileTime As FILETIME, _ 
    lpFileTime As FILETIME) As Long 

Public Declare Function FileTimeToSystemTime Lib _ 
    "kernel32" (lpFileTime As FILETIME, lpSystemTime _ 
    As SYSTEMTIME) As Long 

Public Type FILETIME 
    dwLowDateTime As Long 
    dwHighDateTime As Long 
End Type 

Public 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 

'--------------------------------------------------------------------- 
' Convert ISO8601 dateTimes to Excel Dates 
'--------------------------------------------------------------------- 
Public Function ISODATE(iso As String) 
    ' Find location of delimiters in input string 
    Dim tPos As Integer: tPos = InStr(iso, "T") 
    If tPos = 0 Then tPos = Len(iso) + 1 
    Dim zPos As Integer: zPos = InStr(iso, "Z") 
    If zPos = 0 Then zPos = InStr(iso, "+") 
    If zPos = 0 Then zPos = InStr(tPos, iso, "-") 
    If zPos = 0 Then zPos = Len(iso) + 1 
    If zPos = tPos Then zPos = tPos + 1 

    ' Get the relevant parts out 
    Dim datePart As String: datePart = Mid(iso, 1, tPos - 1) 
    Dim timePart As String: timePart = Mid(iso, tPos + 1, zPos - tPos - 1) 
    Dim dotPos As Integer: dotPos = InStr(timePart, ".") 
    If dotPos = 0 Then dotPos = Len(timePart) + 1 
    timePart = Left(timePart, dotPos - 1) 

    ' Have them parsed separately by Excel 
    Dim d As Date: d = DateValue(datePart) 
    Dim t As Date: If timePart <> "" Then t = TimeValue(timePart) 
    Dim dt As Date: dt = d + t 

    ' Add the timezone 
    Dim tz As String: tz = Mid(iso, zPos) 
    If tz <> "" And Left(tz, 1) <> "Z" Then 
     Dim colonPos As Integer: colonPos = InStr(tz, ":") 
     If colonPos = 0 Then colonPos = Len(tz) + 1 

     Dim minutes As Integer: minutes = CInt(Mid(tz, 2, colonPos - 2)) * 60 + CInt(Mid(tz, colonPos + 1)) 
     If Left(tz, 1) = "+" Then minutes = -minutes 
     dt = DateAdd("n", minutes, dt) 
    End If 

    ' Return value is the ISO8601 date in the local time zone 
    dt = UTCToLocalTime(dt) 
    ISODATE = dt 
End Function 

'--------------------------------------------------------------------- 
' Got this function to convert local date to UTC date from 
' http://excel.tips.net/Pages/T002185_Automatically_Converting_to_GMT.html 
'--------------------------------------------------------------------- 
Public Function UTCToLocalTime(dteTime As Date) As Date 
    Dim infile As FILETIME 
    Dim outfile As FILETIME 
    Dim insys As SYSTEMTIME 
    Dim outsys As SYSTEMTIME 

    insys.wYear = CInt(Year(dteTime)) 
    insys.wMonth = CInt(Month(dteTime)) 
    insys.wDay = CInt(Day(dteTime)) 
    insys.wHour = CInt(Hour(dteTime)) 
    insys.wMinute = CInt(Minute(dteTime)) 
    insys.wSecond = CInt(Second(dteTime)) 

    Call SystemTimeToFileTime(insys, infile) 
    Call FileTimeToLocalFileTime(infile, outfile) 
    Call FileTimeToSystemTime(outfile, outsys) 

    UTCToLocalTime = CDate(outsys.wMonth & "/" & _ 
     outsys.wDay & "/" & _ 
     outsys.wYear & " " & _ 
     outsys.wHour & ":" & _ 
     outsys.wMinute & ":" & _ 
     outsys.wSecond) 
End Function 

'--------------------------------------------------------------------- 
' Tests for the ISO Date functions 
'--------------------------------------------------------------------- 
Public Sub ISODateTest() 
    ' [[ Verify that all dateTime formats parse sucesfully ]] 
    Dim d1 As Date: d1 = ISODATE("2011-01-01") 
    Dim d2 As Date: d2 = ISODATE("2011-01-01T00:00:00") 
    Dim d3 As Date: d3 = ISODATE("2011-01-01T00:00:00Z") 
    Dim d4 As Date: d4 = ISODATE("2011-01-01T12:00:00Z") 
    Dim d5 As Date: d5 = ISODATE("2011-01-01T12:00:00+05:00") 
    Dim d6 As Date: d6 = ISODATE("2011-01-01T12:00:00-05:00") 
    Dim d7 As Date: d7 = ISODATE("2011-01-01T12:00:00.05381+05:00") 
    AssertEqual "Date and midnight", d1, d2 
    AssertEqual "With and without Z", d2, d3 
    AssertEqual "With timezone", -5, DateDiff("h", d4, d5) 
    AssertEqual "Timezone Difference", 10, DateDiff("h", d5, d6) 
    AssertEqual "Ignore subsecond", d5, d7 

    ' [[ Independence of local DST ]] 
    ' Verify that a date in winter and a date in summer parse to the same Hour value 
    Dim w As Date: w = ISODATE("2010-02-23T21:04:48+01:00") 
    Dim s As Date: s = ISODATE("2010-07-23T21:04:48+01:00") 
    AssertEqual "Winter/Summer hours", Hour(w), Hour(s) 

    MsgBox "All tests passed succesfully!" 
End Sub 

Sub AssertEqual(name, x, y) 
    If x <> y Then Err.Raise 1234, Description:="Failed: " & name & ": '" & x & "' <> '" & y & "'" 
End Sub 
+10

:(本應建在 – notJim 2011-04-22 20:32:21

+3

這是黃金+100(如果我能) – 2012-11-06 01:10:20

+0

收到每個`Declare`上加上`PtrSafe`我的系統 – Raman 2017-10-17 23:14:25

5

我會發布此評論作爲評論,但我沒有足夠的代表 - 對不起!這是非常有用的,我 - 感謝rix0rrr,但我注意到,UTCToLocalTime功能需要構建在最後的日期時考慮的區域設置帳戶。下面是我在英國使用的版本 - 請注意,wDay和wMonth的順序顛倒過來:

Public Function UTCToLocalTime(dteTime As Date) As Date 
    Dim infile As FILETIME 
    Dim outfile As FILETIME 
    Dim insys As SYSTEMTIME 
    Dim outsys As SYSTEMTIME 

    insys.wYear = CInt(Year(dteTime)) 
    insys.wMonth = CInt(Month(dteTime)) 
    insys.wDay = CInt(Day(dteTime)) 
    insys.wHour = CInt(Hour(dteTime)) 
    insys.wMinute = CInt(Minute(dteTime)) 
    insys.wSecond = CInt(Second(dteTime)) 

    Call SystemTimeToFileTime(insys, infile) 
    Call FileTimeToLocalFileTime(infile, outfile) 
    Call FileTimeToSystemTime(outfile, outsys) 

    UTCToLocalTime = CDate(outsys.wDay & "/" & _ 
    outsys.wMonth & "/" & _ 
    outsys.wYear & " " & _ 
    outsys.wHour & ":" & _ 
    outsys.wMinute & ":" & _ 
    outsys.wSecond) 
    End Function 
1

我的日期上的形式20130221T133551Z(YYYYMMDD'T'HHMMSS'Z'),所以我創造了這個變種:

Public Function ISODATEZ(iso As String) As Date 
    Dim yearPart As Integer: yearPart = CInt(Mid(iso, 1, 4)) 
    Dim monPart As Integer: monPart = CInt(Mid(iso, 5, 2)) 
    Dim dayPart As Integer: dayPart = CInt(Mid(iso, 7, 2)) 
    Dim hourPart As Integer: hourPart = CInt(Mid(iso, 10, 2)) 
    Dim minPart As Integer: minPart = CInt(Mid(iso, 12, 2)) 
    Dim secPart As Integer: secPart = CInt(Mid(iso, 14, 2)) 
    Dim tz As String: tz = Mid(iso, 16) 

    Dim dt As Date: dt = DateSerial(yearPart, monPart, dayPart) + TimeSerial(hourPart, minPart, secPart) 

    ' Add the timezone 
    If tz <> "" And Left(tz, 1) <> "Z" Then 
     Dim colonPos As Integer: colonPos = InStr(tz, ":") 
     If colonPos = 0 Then colonPos = Len(tz) + 1 

     Dim minutes As Integer: minutes = CInt(Mid(tz, 2, colonPos - 2)) * 60 + CInt(Mid(tz, colonPos + 1)) 
     If Left(tz, 1) = "+" Then minutes = -minutes 
     dt = DateAdd("n", minutes, dt) 
    End If 

    ' Return value is the ISO8601 date in the local time zone 
    ' dt = UTCToLocalTime(dt) 
    ISODATEZ = dt 
End Function 

(時區轉換未測試,並且沒有錯誤處理意外輸入的情況下)

88

有來解析ISO時間戳沒有時區中的(合理)簡單的方式使用公式而不是宏。這不正是什麼原來的海報已經問過,但我發現這個問題,試圖解析ISO時間戳在Excel中,發現this solution有用的時候,所以我想我會在這裏分享。

下面的公式將解析的ISO時間戳,同樣沒有時區:

=DATEVALUE(MID(A1,1,10))+TIMEVALUE(MID(A1,12,8)) 

這將產生浮點格式的時間,這可以接着格式如使用正常的Excel格式的日期。

0

爲什麼這麼多的代碼?這被用Excel公式排序

嘗試了這一點

= DATEVALUE(MID(C2,1,10))+ TIMEVALUE(MID(C2,12,8))

0

answer通過rix0rrr很棒,但它不支持沒有冒號或只有幾小時的時區偏移。我稍微增強,添加了對這些格式的支持功能:

'--------------------------------------------------------------------- 
' Declarations must be at the top -- see below 
'--------------------------------------------------------------------- 
Public Declare Function SystemTimeToFileTime Lib _ 
    "kernel32" (lpSystemTime As SYSTEMTIME, _ 
    lpFileTime As FILETIME) As Long 

Public Declare Function FileTimeToLocalFileTime Lib _ 
    "kernel32" (lpLocalFileTime As FILETIME, _ 
    lpFileTime As FILETIME) As Long 

Public Declare Function FileTimeToSystemTime Lib _ 
    "kernel32" (lpFileTime As FILETIME, lpSystemTime _ 
    As SYSTEMTIME) As Long 

Public Type FILETIME 
    dwLowDateTime As Long 
    dwHighDateTime As Long 
End Type 

Public 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 

'--------------------------------------------------------------------- 
' Convert ISO8601 dateTimes to Excel Dates 
'--------------------------------------------------------------------- 
Public Function ISODATE(iso As String) 
    ' Find location of delimiters in input string 
    Dim tPos As Integer: tPos = InStr(iso, "T") 
    If tPos = 0 Then tPos = Len(iso) + 1 
    Dim zPos As Integer: zPos = InStr(iso, "Z") 
    If zPos = 0 Then zPos = InStr(iso, "+") 
    If zPos = 0 Then zPos = InStr(tPos, iso, "-") 
    If zPos = 0 Then zPos = Len(iso) + 1 
    If zPos = tPos Then zPos = tPos + 1 

    ' Get the relevant parts out 
    Dim datePart As String: datePart = Mid(iso, 1, tPos - 1) 
    Dim timePart As String: timePart = Mid(iso, tPos + 1, zPos - tPos - 1) 
    Dim dotPos As Integer: dotPos = InStr(timePart, ".") 
    If dotPos = 0 Then dotPos = Len(timePart) + 1 
    timePart = Left(timePart, dotPos - 1) 

    ' Have them parsed separately by Excel 
    Dim d As Date: d = DateValue(datePart) 
    Dim t As Date: If timePart <> "" Then t = TimeValue(timePart) 
    Dim dt As Date: dt = d + t 

    ' Add the timezone 
    Dim tz As String: tz = Mid(iso, zPos) 
    If tz <> "" And Left(tz, 1) <> "Z" Then 
     Dim colonPos As Integer: colonPos = InStr(tz, ":") 
     Dim minutes As Integer 
     If colonPos = 0 Then 
      If (Len(tz) = 3) Then 
       minutes = CInt(Mid(tz, 2)) * 60 
      Else 
       minutes = CInt(Mid(tz, 2, 5)) * 60 + CInt(Mid(tz, 4)) 
      End If 
     Else 
      minutes = CInt(Mid(tz, 2, colonPos - 2)) * 60 + CInt(Mid(tz, colonPos + 1)) 
     End If 

     If Left(tz, 1) = "+" Then minutes = -minutes 
     dt = DateAdd("n", minutes, dt) 
    End If 

    ' Return value is the ISO8601 date in the local time zone 
    dt = UTCToLocalTime(dt) 
    ISODATE = dt 
End Function 

'--------------------------------------------------------------------- 
' Got this function to convert local date to UTC date from 
' http://excel.tips.net/Pages/T002185_Automatically_Converting_to_GMT.html 
'--------------------------------------------------------------------- 
Public Function UTCToLocalTime(dteTime As Date) As Date 
    Dim infile As FILETIME 
    Dim outfile As FILETIME 
    Dim insys As SYSTEMTIME 
    Dim outsys As SYSTEMTIME 

    insys.wYear = CInt(Year(dteTime)) 
    insys.wMonth = CInt(Month(dteTime)) 
    insys.wDay = CInt(Day(dteTime)) 
    insys.wHour = CInt(Hour(dteTime)) 
    insys.wMinute = CInt(Minute(dteTime)) 
    insys.wSecond = CInt(Second(dteTime)) 

    Call SystemTimeToFileTime(insys, infile) 
    Call FileTimeToLocalFileTime(infile, outfile) 
    Call FileTimeToSystemTime(outfile, outsys) 

    UTCToLocalTime = CDate(outsys.wMonth & "/" & _ 
     outsys.wDay & "/" & _ 
     outsys.wYear & " " & _ 
     outsys.wHour & ":" & _ 
     outsys.wMinute & ":" & _ 
     outsys.wSecond) 
End Function 

'--------------------------------------------------------------------- 
' Tests for the ISO Date functions 
'--------------------------------------------------------------------- 
Public Sub ISODateTest() 
    ' [[ Verify that all dateTime formats parse sucesfully ]] 
    Dim d1 As Date: d1 = ISODATE("2011-01-01") 
    Dim d2 As Date: d2 = ISODATE("2011-01-01T00:00:00") 
    Dim d3 As Date: d3 = ISODATE("2011-01-01T00:00:00Z") 
    Dim d4 As Date: d4 = ISODATE("2011-01-01T12:00:00Z") 
    Dim d5 As Date: d5 = ISODATE("2011-01-01T12:00:00+05:00") 
    Dim d6 As Date: d6 = ISODATE("2011-01-01T12:00:00-05:00") 
    Dim d7 As Date: d7 = ISODATE("2011-01-01T12:00:00.05381+05:00") 
    Dim d8 As Date: d8 = ISODATE("2011-01-01T12:00:00-0500") 
    Dim d9 As Date: d9 = ISODATE("2011-01-01T12:00:00-05") 
    AssertEqual "Date and midnight", d1, d2 
    AssertEqual "With and without Z", d2, d3 
    AssertEqual "With timezone", -5, DateDiff("h", d4, d5) 
    AssertEqual "Timezone Difference", 10, DateDiff("h", d5, d6) 
    AssertEqual "Ignore subsecond", d5, d7 
    AssertEqual "No colon in timezone offset", d5, d8 
    AssertEqual "No minutes in timezone offset", d5, d9 

    ' [[ Independence of local DST ]] 
    ' Verify that a date in winter and a date in summer parse to the same Hour value 
    Dim w As Date: w = ISODATE("2010-02-23T21:04:48+01:00") 
    Dim s As Date: s = ISODATE("2010-07-23T21:04:48+01:00") 
    AssertEqual "Winter/Summer hours", Hour(w), Hour(s) 

    MsgBox "All tests passed succesfully!" 
End Sub 

Sub AssertEqual(name, x, y) 
    If x <> y Then Err.Raise 1234, Description:="Failed: " & name & ": '" & x & "' <> '" & y & "'" 
End Sub