Necesito analizar un formato de fecha / hora ISO8601 con una zona horaria incluida (de una fuente externa) en Excel / VBA, a una Fecha normal de Excel. Por lo que puedo decir, Excel XP (que es lo que estamos usando) no tiene una rutina para eso incorporado, así que supongo que estoy buscando una función de VBA personalizada para el análisis.
Las fechas y horas de ISO8601 se parecen a una de estas:
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
TryParseExactDate( "yyyy-MM-dd'T'HH:mm:ss", A1 )
función simple en su biblioteca de fórmulas expansiva. ¿Cuál es la excusa de Microsoft? :(Respuestas:
Existe una forma (razonablemente) simple de analizar una marca de tiempo ISO SIN la zona horaria utilizando fórmulas en lugar de macros. Esto no es exactamente lo que ha pedido el póster original, pero encontré esta pregunta al intentar analizar las marcas de tiempo ISO en Excel y encontré esta solución útil, así que pensé en compartirla aquí.
La siguiente fórmula analizará una marca de tiempo ISO, nuevamente SIN la zona horaria:
=DATEVALUE(MID(A1,1,10))+TIMEVALUE(MID(A1,12,8))
Esto producirá la fecha en formato de punto flotante, que luego puede formatear como una fecha usando formatos normales de Excel.
fuente
8
a12
para incluir milisegundos, si lo necesita y su entrada lo incluye.=DATEVALUE(MID(C2,1,10))+TIMEVALUE(MID(C2,12,8))-TIMEVALUE("6:00")
Muchas búsquedas en Google no arrojaron nada, así que escribo mi propia rutina. Publicarlo aquí para referencia futura:
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
fuente
PtrSafe
antes de cada unoDeclare
en mi sistema.Dim d8 As Date: d8 = ISODATE("2020-01-02T16:46:00")
que es una fecha ISO válida para el 2 de enero, devuelve el 1 de febrero ... Sus pruebas son muy optimistas.Hubiera publicado esto como comentario, pero no tengo suficiente representante, ¡lo siento !. Esto fue realmente útil para mí, gracias rix0rrr, pero noté que la función UTCToLocalTime debe tener en cuenta la configuración regional al construir la fecha al final. Aquí está la versión que uso en el Reino Unido; tenga en cuenta que el orden de wDay y wMonth está invertido:
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
fuente
La respuesta de rix0rrr es excelente, pero no admite desplazamientos de zona horaria sin dos puntos o con solo horas. Mejoré ligeramente la función para agregar soporte para estos formatos:
'--------------------------------------------------------------------- ' 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
fuente
Sé que no es tan elegante como el módulo VB, pero si alguien está buscando una fórmula rápida que considere la zona horaria después de '+', entonces podría ser esta.
= DATEVALUE(MID(D3,1,10))+TIMEVALUE(MID(D3,12,5))+TIME(MID(D3,18,2),0,0)
cambiará
2017-12-01T11:03+1100
a
2/12/2017 07:03:00 AM
(hora local considerando la zona horaria)
obviamente, puede modificar la longitud de diferentes secciones de recorte, si también tiene milisegundos o si tiene más tiempo después de +.
use la
sigpwned
fórmula si desea ignorar la zona horaria.fuente
Puede hacer esto sin VB para aplicaciones:
Por ejemplo, para analizar lo siguiente:
2011-01-01T12:00:00+05:00 2011-01-01T12:00:00-05:00
hacer:
=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)))
por
2011-01-01T12:00:00Z
hacer:
=DATEVALUE(LEFT(A1,10))+TIMEVALUE(MID(A1,12,8))
por
2011-01-01
hacer:
=DATEVALUE(LEFT(A1,10))
pero el formato de fecha superior debería analizar Excel automáticamente.
Luego, obtiene un valor de fecha / hora de Excel, que puede formatear a fecha y hora.
Para obtener información detallada y archivos de muestra: http://blog.hani-ibrahim.de/iso-8601-parsing-in-excel-and-calc.html
fuente
Mis fechas están en el formulario 20130221T133551Z (YYYYMMDD'T'HHMMSS'Z ') así que creé esta variante:
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
(la conversión de zona horaria no se prueba y no hay manejo de errores en caso de entrada inesperada)
fuente
Si es suficiente para convertir solo ciertos formatos (fijos) a UTC, puede escribir una función o fórmula VBA simple.
La función / fórmula a continuación funcionará para estos formatos (los milisegundos se omitirán de todos modos):
2011-01-01T12:00:00.053+0500 2011-01-01T12:00:00.05381+0500
Función VBA
Más largo, para una mejor legibilidad:
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
... o un "delineador":
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
Fórmula
=DATEVALUE(LEFT([@ISO], 10)) + TIMEVALUE(MID([@ISO], 12, 8)) - VALUE(RIGHT([@ISO], 5)/100)/24
[@ISO]
es la celda (dentro de una tabla) que contiene la fecha / hora en la hora local en formato ISO8601.Ambos generarán un nuevo valor de tipo de fecha / hora. Siéntase libre de ajustar las funciones según sus necesidades (formato de fecha / hora específico).
fuente