Phân tích cú pháp ngày / giờ ISO8601 (bao gồm TimeZone) trong Excel


84

Tôi cần phân tích cú pháp định dạng ngày / giờ ISO8601 với múi giờ được bao gồm (từ nguồn bên ngoài) trong Excel / VBA, thành Ngày Excel bình thường. Theo như tôi có thể nói, Excel XP (là những gì chúng tôi đang sử dụng) không có quy trình tích hợp sẵn đó, vì vậy tôi đoán tôi đang xem xét một hàm VBA tùy chỉnh để phân tích cú pháp.

Lịch ngày của ISO8601 trông giống như một trong số sau:

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

1
Bây giờ là năm 2020 và phiên bản mới nhất của Excel thông qua Office 365 vẫn không có một TryParseExactDate( "yyyy-MM-dd'T'HH:mm:ss", A1 )chức năng đơn giản nào trong thư viện công thức mở rộng. Microsoft là gì? :(
Đài

Câu trả lời:


167

Có một cách đơn giản (hợp lý) để phân tích một dấu thời gian ISO KHÔNG Múi giờ bằng cách sử dụng công thức thay vì macro. Đây không phải là chính xác những gì người đăng ban đầu đã hỏi, nhưng tôi đã tìm thấy câu hỏi này khi cố gắng phân tích dấu thời gian ISO trong Excel và thấy giải pháp này hữu ích, vì vậy tôi nghĩ tôi sẽ chia sẻ nó ở đây.

Công thức sau sẽ phân tích cú pháp một dấu thời gian ISO, một lần nữa KHÔNG có múi giờ:

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

Điều này sẽ tạo ra ngày tháng ở định dạng dấu phẩy động, sau đó bạn có thể định dạng ngày tháng bằng các định dạng Excel thông thường.


3
Kỳ lạ là điều này đã không trở thành câu trả lời được chấp nhận. Nó đơn giản hơn nhiều so với phần còn lại.
Travis Griggs

6
Nhưng giải pháp này không xem xét việc chuyển đổi múi giờ.
Goku

1
Đây là một giải pháp thay thế hợp lý nếu múi giờ không liên quan hoặc giống nhau, ví dụ: múi giờ địa phương.
kevinarpe

5
Bạn có thể thay đổi giá trị đó 8thành a 12để bao gồm mili giây, nếu bạn cần và thông tin đầu vào của bạn bao gồm nó.
gilly 3

3
Tôi đã sử dụng cái này để chuyển đổi mã thời gian. Chỉ cần đặt chênh lệch HH: MM trong phần cuối cùng và cộng hoặc trừ tùy thuộc vào múi giờ. Trong trường hợp của tôi, tôi chậm hơn 6 giờ nên tôi trừ đi. =DATEVALUE(MID(C2,1,10))+TIMEVALUE(MID(C2,12,8))-TIMEVALUE("6:00")
chaiboy

44

Rất nhiều Googling không hiển thị bất cứ điều gì nên tôi viết quy trình của riêng mình. Đăng nó ở đây để tham khảo trong tương lai:

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

Đã phải thêm PtrSafetrước mỗi Declaretrên hệ thống của tôi.
Raman

1
Vâng, điều này không hoạt động. Nếu bạn thêm một bài kiểm tra Dim d8 As Date: d8 = ISODATE("2020-01-02T16:46:00")là ngày ISO hợp lệ vào ngày 2 tháng 1, bit nó sẽ trả về ngày 1 tháng 2 ... Các bài kiểm tra của bạn rất lạc quan.
Liam,

5

Tôi đã đăng điều này như một bình luận, nhưng tôi không có đủ đại diện - xin lỗi !. Điều này thực sự hữu ích đối với tôi - cảm ơn rix0rrr, nhưng tôi nhận thấy rằng hàm UTCToLocalTime cần tính đến cài đặt khu vực khi tạo ngày ở cuối. Đây là phiên bản tôi sử dụng ở Vương quốc Anh - lưu ý rằng thứ tự của wDay và wMonth được đảo ngược:

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

Tôi muốn chỉ ra rằng tác giả đã hỏi về chuỗi ngày giờ được định dạng ISO8601, các chuỗi này nhất quán trong thứ tự trường. Chắc chắn thật tuyệt khi dữ liệu của bạn phù hợp với dữ liệu của bạn, nhưng nếu ai khác đọc được điều này và cảm thấy bối rối, bạn nên xem en.wikipedia.org/wiki/ISO_8601 và cả xkcd.com/1179 .
Hovis Biddle

2
Ái chà! Vụ nổ từ quá khứ. Dù sao, tôi đã không thay đổi bất cứ điều gì về thứ tự trường của ngày ISO. Đây là phiên bản địa phương cần tuân theo các quy ước địa phương. Lý tưởng nhất là mã nên tìm ra điều này, nhưng tôi đã nói điều này là để sử dụng ở Anh ...
dsl101

2

Câu trả lời của rix0rrr là tuyệt vời, nhưng nó không hỗ trợ việc lệch múi giờ không có dấu hai chấm hoặc chỉ có giờ. Tôi đã nâng cao một chút chức năng để thêm hỗ trợ cho các định dạng này:

'---------------------------------------------------------------------
' 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

2

Tôi biết nó không thanh lịch như mô-đun VB nhưng nếu ai đó đang tìm kiếm một công thức nhanh có tính đến múi giờ sau '+' thì đây có thể là nó.

= DATEVALUE(MID(D3,1,10))+TIMEVALUE(MID(D3,12,5))+TIME(MID(D3,18,2),0,0)

sẽ thay đổi

2017-12-01T11:03+1100

đến

2/12/2017 07:03:00 AM

(giờ địa phương xem xét múi giờ)

rõ ràng, bạn có thể sửa đổi độ dài của các phần cắt khác nhau, nếu bạn cũng có mili giây hoặc nếu bạn có thời gian lâu hơn sau dấu +.

sử dụng sigpwnedcông thức nếu bạn muốn bỏ qua múi giờ.


2

Bạn có thể làm điều này với VB cho Ứng dụng:

Ví dụ: để phân tích cú pháp sau:

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

làm:

=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)))

Đối với

2011-01-01T12:00:00Z

làm:

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

Đối với

2011-01-01

làm:

=DATEVALUE(LEFT(A1,10))

nhưng định dạng ngày trên sẽ được Excel tự động phân tích cú pháp.

Sau đó, bạn nhận được một giá trị ngày / giờ Excel, bạn có thể định dạng ngày và giờ.

Để biết thông tin chi tiết và tệp mẫu: http://blog.hani-ibrahim.de/iso-8601-parsing-in-excel-and-calc.html


tiếc là liên kết cho lời giải với Z ở cuối không tồn tại nữa. @hani - bạn có muốn chèn trực tiếp giải pháp để câu trả lời này giữ nguyên giá trị của nó không?
luksch

1

Ngày tháng của tôi có dạng 20130221T133551Z (YYYYMMDD'T'HHMMSS'Z ') vì vậy tôi đã tạo biến thể này:

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

(chuyển đổi múi giờ không được kiểm tra và không có xử lý lỗi trong trường hợp nhập không mong muốn)


0

Nếu đủ để bạn chỉ chuyển đổi một số định dạng (cố định) sang UTC, bạn có thể viết một hàm hoặc công thức VBA đơn giản.

Hàm / công thức bên dưới sẽ hoạt động đối với các định dạng này (mili giây vẫn sẽ bị bỏ qua):

2011-01-01T12:00:00.053+0500
2011-01-01T12:00:00.05381+0500

Hàm VBA

Dài hơn, để dễ đọc hơn:

Public Function CDateUTC(dISO As String) As Date

  Dim d, t, tz As String
  Dim tzInt As Integer
  Dim dLocal As Date

  d = Left(dISO, 10)
  t = Mid(dISO, 12, 8)
  tz = Right(dISO, 5)
  tzInt = - CInt(tz) \ 100
  dLocal = CDate(d & " " & t)

  CDateUTC = DateAdd("h", tzInt, dLocal)    

End Function

... hoặc "oneliner":

Public Function CDateUTC(dISO As String) As Date
  CDateUTC = DateAdd("h", -CInt(Right(dISO, 5)) \ 100, CDate(Left(dISO, 10) & " " & Mid(dISO, 12, 8)))    
End Function

Công thức

=DATEVALUE(LEFT([@ISO], 10)) + TIMEVALUE(MID([@ISO], 12, 8)) - VALUE(RIGHT([@ISO], 5)/100)/24

[@ISO] là ô (trong bảng) chứa ngày / giờ theo giờ địa phương ở định dạng ISO8601.

Cả hai sẽ tạo ra giá trị loại ngày / giờ mới. Hãy thoải mái điều chỉnh các chức năng phù hợp với nhu cầu của bạn (định dạng ngày / giờ cụ thể).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.