在Excel中解析ISO8601日期/时间(包括TimeZone)

rix*_*rrr 75 excel timezone datetime vba iso8601

我需要使用Excel/VBA中包含的时区(从外部源)解析ISO8601日期/时间格式到正常的Excel日期.据我所知,Excel XP(我们正在使用的)没有内置的例程,所以我想我正在寻找一个用于解析的自定义VBA函数.

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
Run Code Online (Sandbox Code Playgroud)

sig*_*ned 139

有一种(合理)简单的方法使用公式而不是宏来解析没有时区的ISO时间戳.这不正是什么原来的海报已经问过,但我发现,试图解析在Excel ISO时间戳的时候这个问题,并发现该解决方案是有用的,所以我想我会在这里分享.

以下公式将解析ISO时间戳,再次解析时区:

=DATEVALUE(MID(A1,1,10))+TIMEVALUE(MID(A1,12,8))
Run Code Online (Sandbox Code Playgroud)

这将以浮点格式生成日期,然后您可以使用常规Excel格式将其格式化为日期.

  • 我用它来转换时间码。只需将 HH:MM 差异放在最后一部分,然后根据时区添加或减去。就我而言,我落后了 6 小时,因此我将其减去。```=DATEVALUE(MID(C2,1,10))+TIMEVALUE(MID(C2,12,8))-TIMEVALUE("6:00")``` (5认同)
  • 但是这个解决方案不考虑时区转换. (4认同)
  • 奇怪的是,这并没有成为公认的答案.它比其他的简单得多. (3认同)
  • 时区转换的另一个选项是简单地添加(或减去)以小时为单位的偏移量作为 24 的一部分(例如 `=DATEVALUE(MID(C2,1,10))+TIMEVALUE(MID(C2,12,8))- (5/24)`从 UTC 转换为 EST)。 (3认同)
  • 如果时区不相关或完全相同,例如本地时区,这是一个合理的选择。 (2认同)
  • 如果需要,您可以将“ 8”更改为“ 12”以包含毫秒,并且您的输入也包含毫秒。 (2认同)
  • 如果您使用法语 Excel,则相同的公式为:=DATEVAL(STXT(A1;1;10))+TEMPSVAL(STXT(A1;12;8)) (2认同)

rix*_*rrr 41

很多谷歌搜索没有发现任何东西,所以我写了自己的例程.将其发布在此处以供将来参考:

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
Run Code Online (Sandbox Code Playgroud)

  • 是的,这不起作用。如果你添加一个测试`Dim d8 As Date: d8 = ISODATE("2020-01-02T16:46:00")`,它是 1 月 2 日的有效 ISO 日期,它返回 1 月 1 日......你的测试是非常乐观。 (3认同)

dsl*_*101 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
Run Code Online (Sandbox Code Playgroud)

  • 哇!过去的冲击波。无论如何,我对ISO日期的字段顺序没有任何更改。这是_local_版本,需要遵循本地约定。理想情况下,代码应该可以解决这个问题,但是我确实说过这是在英国使用的... (2认同)

Abs*_*Abs 5

我知道它不像 VB 模块那么优雅,但是如果有人正在寻找一个快速公式,该公式也考虑了 '+' 之后的时区,那么这可能就是它。

= DATEVALUE(MID(D3,1,10))+TIMEVALUE(MID(D3,12,5))+TIME(MID(D3,18,2),0,0)
Run Code Online (Sandbox Code Playgroud)

将改变

2017-12-01T11:03+1100
Run Code Online (Sandbox Code Playgroud)

2/12/2017 07:03:00 AM
Run Code Online (Sandbox Code Playgroud)

(考虑时区的当地时间)

显然,你可以修改不同修剪部分的长度,如果你也有毫秒,或者如果你在+之后有更长的时间。

sigpwned如果您想忽略时区,请使用公式。


小智 5

您可以在不使用 VB for Applications 的情况下执行此操作:

例如解析以下内容:

2011-01-01T12:00:00+05:00
2011-01-01T12:00:00-05:00
Run Code Online (Sandbox Code Playgroud)

做:

=IF(MID(A1,20,1)="+",TIMEVALUE(MID(A1,21,5))+DATEVALUE(LEFT(A1,10))+TIMEVALUE(MID(A1,12,8)),-TIMEVALUE(MID(A1,21,5))+DATEVALUE(LEFT(A1,10))+TIMEVALUE(MID(A1,12,8)))
Run Code Online (Sandbox Code Playgroud)

为了

2011-01-01T12:00:00Z
Run Code Online (Sandbox Code Playgroud)

做:

=DATEVALUE(LEFT(A1,10))+TIMEVALUE(MID(A1,12,8))
Run Code Online (Sandbox Code Playgroud)

为了

2011-01-01
Run Code Online (Sandbox Code Playgroud)

做:

=DATEVALUE(LEFT(A1,10))
Run Code Online (Sandbox Code Playgroud)

但上面的日期格式应该 Excel 自动解析。

然后您将获得一个 Excel 日期/时间值,您可以将其格式化为日期和时间。

有关详细信息和示例文件:http://blog.hani-ibrahim.de/iso-8601-parsing-in-excel-and-calc.html