visual tutorial for español applications vba vb6

vba - tutorial - La función IsDate devuelve resultados inesperados



visual basic for applications download (2)

Esta pequeña "función" me hizo tropezar recientemente y quería concienciar sobre algunos de los problemas que rodean la función IsDate en VB y VBA.

El caso simple

Como era de esperar, IsDate devuelve True cuando pasó un tipo de datos de fecha y False para todos los demás tipos de datos, excepto Strings. Para Strings, IsDate devuelve True o False según el contenido de la cadena:

IsDate(CDate("1/1/1980")) --> True IsDate(#12/31/2000#) --> True IsDate(12/24) --> False ''12/24 evaluates to a Double: 0.5'' IsDate("Foo") --> False IsDate("12/24") --> True

IsDateTime?

IsDate debe IsDateTime mayor precisión IsDateTime porque devuelve True para cadenas formateadas como veces:

IsDate("10:55 AM") --> True IsDate("23:30") --> True ''CDate("23:30") --> 11:30:00 PM'' IsDate("1:30:59") --> True ''CDate("1:30:59") --> 1:30:59 AM'' IsDate("13:55 AM") --> True ''CDate("13:55 AM")--> 1:55:00 PM'' IsDate("13:55 PM") --> True ''CDate("13:55 PM")--> 1:55:00 PM''

Nota de los dos últimos ejemplos anteriores que IsDate no es un validador perfecto de los tiempos.

¡El Gotcha!

IsDate no solo acepta tiempos, acepta tiempos en muchos formatos. Uno de los cuales usa un punto ( . ) Como separador. Esto lleva a cierta confusión, porque el período se puede usar como un separador de tiempo pero no como un separador de fecha:

IsDate("13.50") --> True ''CDate("13.50") --> 1:50:00 PM'' IsDate("12.25") --> True ''CDate("12.25") --> 12:25:00 PM'' IsDate("12.25.10") --> True ''CDate("12.25.10") --> 12:25:10 PM'' IsDate("12.25.2010")--> False ''2010 > 59 (number of seconds in a minute - 1)'' IsDate("24.12") --> False ''24 > 23 (number of hours in a day - 1)'' IsDate("0.12") --> True ''CDate("0.12") --> 12:12:00 AM

Esto puede ser un problema si está analizando una cadena y operando en ella en función de su tipo aparente. Por ejemplo:

Function Bar(Var As Variant) If IsDate(Var) Then Bar = "This is a date" ElseIf IsNumeric(Var) Then Bar = "This is numeric" Else Bar = "This is something else" End If End Function ?Bar("12.75") --> This is numeric ?Bar("12.50") --> This is a date

Las soluciones

Si está probando una variante para su tipo de datos subyacente, debe usar TypeName(Var) = "Date" lugar de IsDate(Var) :

TypeName(#12/25/2010#) --> Date TypeName("12/25/2010") --> String Function Bar(Var As Variant) Select Case TypeName(Var) Case "Date" Bar = "This is a date type" Case "Long", "Double", "Single", "Integer", "Currency", "Decimal", "Byte" Bar = "This is a numeric type" Case "String" Bar = "This is a string type" Case "Boolean" Bar = "This is a boolean type" Case Else Bar = "This is some other type" End Select End Function ?Bar("12.25") --> This is a string type ?Bar(#12/25#) --> This is a date type ?Bar(12.25) --> This is a numeric type

Sin embargo, si se trata de cadenas que pueden ser fechas o números (por ejemplo, analizar un archivo de texto), debe verificar si se trata de un número antes de verificar para ver si se trata de una fecha:

Function Bar(Var As Variant) If IsNumeric(Var) Then Bar = "This is numeric" ElseIf IsDate(Var) Then Bar = "This is a date" Else Bar = "This is something else" End If End Function ?Bar("12.75") --> This is numeric ?Bar("12.50") --> This is numeric ?Bar("12:50") --> This is a date

Incluso si lo único que te importa es si se trata de una fecha, probablemente deberías asegurarte de que no sea un número:

Function Bar(Var As Variant) If IsDate(Var) And Not IsNumeric(Var) Then Bar = "This is a date" Else Bar = "This is something else" End If End Function ?Bar("12:50") --> This is a date ?Bar("12.50") --> This is something else

Peculiaridades de CDate

Como señaló @Deanna en los comentarios a continuación, el comportamiento de CDate() es confiable. Sus resultados varían según si se le pasa una cadena o un número:

?CDate(0.5) --> 12:00:00 PM ?CDate("0.5") --> 12:05:00 AM

Los ceros finales y iniciales son significativos si un número se pasa como una cadena:

?CDate(".5") --> 12:00:00 PM ?CDate("0.5") --> 12:05:00 AM ?CDate("0.50") --> 12:50:00 AM ?CDate("0.500") --> 12:00:00 PM

El comportamiento también cambia a medida que la parte decimal de una cadena se acerca a la marca de los 60 minutos:

?CDate("0.59") --> 12:59:00 AM ?CDate("0.60") --> 2:24:00 PM

La conclusión es que si necesita convertir cadenas a la fecha / hora, debe conocer el formato en el que espera que estén y, luego, formatearlas adecuadamente antes de confiar en CDate() para convertirlas.

¿Cómo es que IsDate("13.50") devuelve True pero IsDate("12.25.2010") devuelve False ?


Tarde en el juego aquí (mwolfe02 respondió esto hace un año!) Pero el problema sigue siendo real, hay enfoques alternativos que vale la pena investigar, y es el lugar para encontrarlos: así que aquí está mi propia respuesta ...

Me tropecé con VBA.IsDate () en este mismo tema hace unos años, y codifiqué una función extendida para cubrir casos que VBA.IsDate () maneja mal. El peor es que los flotantes y enteros devuelven FALSE desde IsDate, aunque las publicaciones seriadas de fechas se pasan con frecuencia como Dobles (para DateTime) y Long Integers (para fechas).

Un punto a tener en cuenta: es posible que su implementación no requiera la capacidad de verificar variantes de matriz. De lo contrario, no dude en quitar el código en el bloque sangrado que sigue a Else '' Comment this out if you don''t need to check array variants . Sin embargo, debe tener en cuenta que algunos sistemas de terceros (incluidos clientes de datos de mercado en tiempo real) devuelven sus datos en matrices, incluso puntos de datos únicos.

Más información está en los comentarios del código.

Aquí está el Código:

Public Function IsDateEx(TestDate As Variant, Optional LimitPastDays As Long = 7305, Optional LimitFutureDays As Long = 7305, Optional FirstColumnOnly As Boolean = False) As Boolean ''Attribute IsDateEx.VB_Description = "Returns TRUE if TestDate is a date, and is within ± 20 years of the system date. ''Attribute IsDateEx.VB_ProcData.VB_Invoke_Func = "w/n9" Application.Volatile False On Error Resume Next '' Returns TRUE if TestDate is a date, and is within ± 20 years of the system date. '' This extends VBA.IsDate(), which returns FALSE for floating-point numbers and integers '' even though the VBA Serial Date is a Double. IsDateEx() returns TRUE for variants that '' can be parsed into string dates, and numeric values with equivalent date serials. All '' values must still be ±20 years from SysDate. Note: locale and language settings affect '' the validity of day- and month names; and partial date strings (eg: ''01 January'') will '' be parsed with the missing components filled-in with system defaults. '' Optional parameters LimitPastDays/LimitFutureDays vary the default ± 20 years boundary '' Note that an array variant is an acceptable input parameter: IsDateEx will return TRUE '' if all the values in the array are valid dates: set FirstColumnOnly:=TRUE if you only '' need to check the leftmost column of a 2-dimensional array. '' * THIS CODE IS IN THE PUBLIC DOMAIN '' * '' * Author: Nigel Heffernan, May 2005 '' * http://excellerando.blogspot.com/ '' * '' * '' * ********************************* Dim i As Long Dim j As Long Dim k As Long Dim jStart As Long Dim jEnd As Long Dim dateFirst As Date Dim dateLast As Date Dim varDate As Variant dateFirst = VBA.Date - LimitPastDays dateLast = VBA.Date + LimitFutureDays IsDateEx = False If TypeOf TestDate Is Excel.Range Then TestDate = TestDate.Value2 End If If VarType(TestDate) < vbArray Then If IsDate(TestDate) Or IsNumeric(TestDate) Then If (dateLast > TestDate) And (TestDate > dateFirst) Then IsDateEx = True End If End If Else '' Comment this out if you don''t need to check array variants k = ArrayDimensions(TestDate) Select Case k Case 1 IsDateEx = True For i = LBound(TestDate) To UBound(TestDate) If IsDate(TestDate(i)) Or IsNumeric(TestDate(i)) Then If Not ((dateLast > CVDate(TestDate(i))) And (CVDate(TestDate(i)) > dateFirst)) Then IsDateEx = False Exit For End If Else IsDateEx = False Exit For End If Next i Case 2 IsDateEx = True jStart = LBound(TestDate, 2) If FirstColumnOnly Then jEnd = LBound(TestDate, 2) Else jEnd = UBound(TestDate, 2) End If For i = LBound(TestDate, 1) To UBound(TestDate, 1) For j = jStart To jEnd If IsDate(TestDate(i, j)) Or IsNumeric(TestDate(i, j)) Then If Not ((dateLast > CVDate(TestDate(i, j))) And (CVDate(TestDate(i, j)) > dateFirst)) Then IsDateEx = False Exit For End If Else IsDateEx = False Exit For End If Next j Next i Case Is > 2 '' Warning: For... Each enumerations are SLOW For Each varDate In TestDate If IsDate(varDate) Or IsNumeric(varDate) Then If Not ((dateLast > CVDate(varDate)) And (CVDate(varDate) > dateFirst)) Then IsDateEx = False Exit For End If Else IsDateEx = False Exit For End If Next varDate End Select End If End Function

Un consejo para las personas que todavía usan Excel 2003:

Si usted (o sus usuarios) van a llamar a IsDateEx () desde una hoja de trabajo, coloque estas dos líneas, inmediatamente debajo del encabezado de la función, usando un editor de texto en un archivo .bas exportado y reimportar el archivo, porque los atributos de VB son útiles , pero no son accesibles para el editor de código en el IDE de VBA de Excel :

Attribute IsDateEx.VB_Description = "Returns TRUE if TestDate is a date, and is within ± 20 years of the system date./r/nChange the defaulte default ± 20 years boundaries by setting values for LimitPastDays and LimitFutureDays/r/nIf you are checking an array of dates, ALL the values will be tested: set FirstColumnOnly TRUE to check the leftmost column only."

Eso es una sola línea: ¡cuidado con los saltos de línea insertados por el navegador! ... Y esta línea, que pone isDateEX en el asistente de función en la categoría ''Información'', junto con ISNUMBER (), ISERR (), ISTEXT () y así sucesivamente:

Attribute IsDateEx.VB_ProcData.VB_Invoke_Func = "w/n9"

Utilice "w / n2" si prefiere verlo en las funciones de Fecha y hora: supera al perderlo en el marasmo de las funciones "Definidas usadas" de su propio código, y todos los complementos de terceros desarrollados por personas que no hacen lo suficiente para ayudar a los usuarios ocasionales.

No tengo idea si esto todavía funciona en Office 2010.

Además, es posible que necesite la fuente para ArrayDimensions:

Esta declaración de API es obligatoria en el encabezado del módulo:

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _ (Destination As Any, _ Source As Any, _ ByVal Length As Long)

... Y aquí está la función en sí:

Private Function ArrayDimensions(arr As Variant) As Integer ''----------------------------------------------------------------- '' will return: '' -1 if not an array '' 0 if an un-dimmed array '' 1 or more indicating the number of dimensions of a dimmed array ''----------------------------------------------------------------- '' Retrieved from Chris Rae''s VBA Code Archive - http://chrisrae.com/vba '' Code written by Chris Rae, 25/5/00 '' Originally published by R. B. Smissaert. '' Additional credits to Bob Phillips, Rick Rothstein, and Thomas Eyde on VB2TheMax Dim ptr As Long Dim vType As Integer Const VT_BYREF = &H4000& ''get the real VarType of the argument ''this is similar to VarType(), but returns also the VT_BYREF bit CopyMemory vType, arr, 2 ''exit if not an array If (vType And vbArray) = 0 Then ArrayDimensions = -1 Exit Function End If ''get the address of the SAFEARRAY descriptor ''this is stored in the second half of the ''Variant parameter that has received the array CopyMemory ptr, ByVal VarPtr(arr) + 8, 4 ''see whether the routine was passed a Variant ''that contains an array, rather than directly an array ''in the former case ptr already points to the SA structure. ''Thanks to Monte Hansen for this fix If (vType And VT_BYREF) Then '' ptr is a pointer to a pointer CopyMemory ptr, ByVal ptr, 4 End If ''get the address of the SAFEARRAY structure ''this is stored in the descriptor ''get the first word of the SAFEARRAY structure ''which holds the number of dimensions ''...but first check that saAddr is non-zero, otherwise ''this routine bombs when the array is uninitialized If ptr Then CopyMemory ArrayDimensions, ByVal ptr, 2 End If End Function

Guarde los agradecimientos en su código fuente: a medida que avance en su carrera como desarrollador, llegará a apreciar que sus propias contribuciones sean reconocidas.

Además: le aconsejo que mantenga esa declaración privada. Si debe convertirlo en un Sub público en otro módulo, inserte la declaración de Option Private Module en el encabezado del módulo. Realmente no desea que sus usuarios llamen a ninguna función con CopyMemoryoperations y aritmética de punteros.