Анализ даты/времени ISO8601 (включая часовой пояс) в Excel
Мне нужно проанализировать формат даты/времени ISO8601 с включенным часовым поясом (из внешнего источника) в Excel/VBA до обычной даты 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
6 ответов:
существует (разумно) простой способ проанализировать временную метку ISO без часового пояса, используя формулы вместо макросов. Это не ровно что задал оригинальный плакат, но я нашел этот вопрос при попытке разобрать временные метки ISO в Excel и нашел данное решение полезно, поэтому я подумал, что поделюсь им здесь.
следующая формула будет анализировать метку времени ISO, опять же без часового пояса:
=DATEVALUE(MID(A1,1,10))+TIMEVALUE(MID(A1,12,8))
Это даст дата в формате с плавающей запятой, которую затем можно отформатировать как дату, используя обычные форматы Excel.
много Googling ничего не получилось, поэтому я пишу свою собственную рутину. Размещение его здесь для дальнейшего использования:
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
Я бы разместил это как комментарий, но у меня недостаточно репутации - извините!. Это было действительно полезно для меня-спасибо 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
мои даты в форме 20130221T133551Z (ГГГГММДД не'HHMMSS такие), поэтому я создал такой вариант:
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
(преобразование часового пояса не тестируется, и нет обработки ошибок в случае неожиданного ввода)
на ответ by 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
Я знаю, что это не так элегантно, как модуль VB но если кто-то ищет быструю формулу, которая учитывает часовой пояс после"+", то это может быть так.
= DATEVALUE(MID(D3,1,10))+TIMEVALUE(MID(D3,12,5))+TIME(MID(D3,18,2),0,0)
изменится
2017-12-01T11:03+1100
до
2/12/2017 07:03:00 AM
(местное время с учетом часового пояса)
очевидно, что u может изменить длину различных секций обрезки, Если у вас есть миллисекунды, а также, если вы получили больше времени после +.
использовать
sigpwned
формула, если вы хотите игнорировать часовой пояс.