Excel VBA date formats - vba

I've got a spreadsheet that contains a number of dates. These generally appear in either mm/dd/yyyy or mm/dd/yyyy hh:mm.
The problem is that the dates aren't always put in correctly and I want to have checks to make sure they are dates in the code.
My original thought was to use IsDate to check or CDate but this didn't seem to work: it was still returning strings instead of dates.
I've since set up a small experiment which shows that these functions don't work the way I expect them to. Methodology is:
In a cell A1 I enter the formula =DATE(2013,10,28)
Cell B1 formula =A1*1 which should equal a number (41575)
Run this little script
Sub test()
MsgBox ("Start:" & TypeName(ActiveCell.Value) & " " & IsDate(ActiveCell.Value))
ActiveCell.Value = Format(ActiveCell.Value, "mm/dd/yyyy")
MsgBox ("After format: " & TypeName(ActiveCell.Value) & " " & IsDate(ActiveCell.Value))
ActiveCell.Value = CDate(ActiveCell.Value)
MsgBox ("After Cdate: " & TypeName(ActiveCell.Value) & " " & IsDate(ActiveCell.Value))
End Sub
When the script starts the cell is a of type date and IsDate returns true. After it is run through Format it is of type string but IsDate still returns true. CDate will also convert the cell to a string. Cell B1 will also now return 0 (since its a string*1).
So I guess to summarize the questions:
Why are Format and CDate changing my cells to strings?
How can I ensure that a cell will return a date value and not just a string that looks like a date?

It's important to distinguish between the content of cells, their display format, the data type read from cells by VBA, and the data type written to cells from VBA and how Excel automatically interprets this. (See e.g. this previous answer.) The relationship between these can be a bit complicated, because Excel will do things like interpret values of one type (e.g. string) as being a certain other data type (e.g. date) and then automatically change the display format based on this. Your safest bet it do everything explicitly and not to rely on this automatic stuff.
I ran your experiment and I don't get the same results as you do. My cell A1 stays a Date the whole time, and B1 stays 41575. So I can't answer your question #1. Results probably depend on how your Excel version/settings choose to automatically detect/change a cell's number format based on its content.
Question #2, "How can I ensure that a cell will return a date value": well, not sure what you mean by "return" a date value, but if you want it to contain a numerical value that is displayed as a date, based on what you write to it from VBA, then you can either:
Write to the cell a string value that you hope Excel will automatically interpret as a date and format as such. Cross fingers. Obviously this is not very robust. Or,
Write a numerical value to the cell from VBA (obviously a Date type is the intended type, but an Integer, Long, Single, or Double could do as well) and explicitly set the cells' number format to your desired date format using the .NumberFormat property (or manually in Excel). This is much more robust.
If you want to check that existing cell contents can be displayed as a date, then here's a function that will help:
Function CellContentCanBeInterpretedAsADate(cell As Range) As Boolean
Dim d As Date
On Error Resume Next
d = CDate(cell.Value)
If Err.Number <> 0 Then
CellContentCanBeInterpretedAsADate = False
Else
CellContentCanBeInterpretedAsADate = True
End If
On Error GoTo 0
End Function
Example usage:
Dim cell As Range
Set cell = Range("A1")
If CellContentCanBeInterpretedAsADate(cell) Then
cell.NumberFormat = "mm/dd/yyyy hh:mm"
Else
cell.NumberFormat = "General"
End If

Format converts the values to strings. IsDate still returns true because it can parse that string and get a valid date.
If you don't want to change the cells to string, don't use Format. (IOW, don't convert them to strings in the first place.) Use the Cell.NumberFormat, and set it to the date format you want displayed.
ActiveCell.NumberFormat = "mm/dd/yy" ' Outputs 10/28/13
ActiveCell.NumberFormat = "dd/mm/yyyy" ' Outputs 28/10/2013

Thanks for the input. I'm obviously seeing some issues that aren't being replicated on others machines. Based on Jean's answer I have come up with less elegant solution that seems to work.
Since if I pass the cell a value directly from cdate, or just format it as a number it leaves the cell value as a string I've had to pass the date value into a numerical variable before passing that number back to the cell.
Function CellContentCanBeInterpretedAsADate(cell As Range) As Boolean
Dim d As Date
On Error Resume Next
d = CDate(cell.Value)
If Err.Number <> 0 Then
CellContentCanBeInterpretedAsADate = False
Else
CellContentCanBeInterpretedAsADate = True
End If
On Error GoTo 0
End Function
Example usage:
Dim cell As Range
dim cvalue as double
Set cell = Range("A1")
If CellContentCanBeInterpretedAsADate(cell) Then
cvalue = cdate(cell.value)
cell.value = cvalue
cell.NumberFormat = "mm/dd/yyyy hh:mm"
Else
cell.NumberFormat = "General"
End If

Use value(cellref) on the side to evaluate the cells. Strings will produce the "#Value" error, but dates resolve to a number (e.g. 43173).

To ensure that a cell will return a date value and not just a string that looks like a date, first you must set the NumberFormat property to a Date format, then put a real date into the cell's content.
Sub test_date_or_String()
Set c = ActiveCell
c.NumberFormat = "#"
c.Value = CDate("03/04/2014")
Debug.Print c.Value & " is a " & TypeName(c.Value) 'C is a String
c.NumberFormat = "m/d/yyyy"
Debug.Print c.Value & " is a " & TypeName(c.Value) 'C is still a String
c.Value = CDate("03/04/2014")
Debug.Print c.Value & " is a " & TypeName(c.Value) 'C is a date
End Sub

Related

VBA: Search for a value in a column, find the next cell that match the value if adjacent cell not empty

I'm pretty newbie at coding in vba (i'm actually learning on the spot as i've been required to do it so), and I'm having a little trouble getting this right.
What I need is to be able to search for a value {clave} and then insert the current date into the adjacent cell, but I haven't found the way to do it without having it overwrite the very first match.
At first I thought I could do it with a Loop, but I can't quite put my finger on it and I've been running in circles.
Is I haven't found the solution, I just left it as it is, but heres my code:
Private Sub buscarbtn_Click()
Dim clv1
Dim rnng As Range
clv1 = clavebx.Value
'Insert date
prontuario1.Range("V:Z").Find(what:=clv1, LookIn:=xlValues, LookAt:=xlWhole).Offset(0, -6).Value = Date
'This isn't really relevant, just calling some data into the userform
busbox.Value = Hoja4.Range("D7").Value
mrcbox.Value = Hoja4.Range("D5").Value
corridabox.Value = Hoja4.Range("D8").Value
namebox.Value = Hoja4.Range("D4") & " - " & Hoja4.Range("D6")
fechabox.Value = Date
End Sub
And a quick look at my table so you can picture what I'm trying to do.
Thank you in advance!
Once you found the CLAVE ID check if the cell value is empty, if not empty place the date value.
Private Sub buscarbtn_Click()
Dim clv1 As String
Dim rnng As Range
Dim clave_found As Range
clv1 = clavebx.Value
'Insert date
Set clave_found = prontuario1.Range("V:Z").Find(what:=clv1, LookIn:=xlValues, LookAt:=xlWhole).Offset(0, -6)
With clave_found
If .Value = vbNullString Then
.Value = Date
Else
MsgBox "The [CLAVE] ID found with date: " & .Value
End If
End With
'This isn't really relevant, just calling some data into the userform
busbox.Value = Hoja4.Range("D7").Value
mrcbox.Value = Hoja4.Range("D5").Value
corridabox.Value = Hoja4.Range("D8").Value
namebox.Value = Hoja4.Range("D4") & " - " & Hoja4.Range("D6")
fechabox.Value = Date
End Sub

Compare dates to fill a cell using VBA Excel

I'm trying to compare two dates with the format DD/MM/YYYY HH:MM AM/PM, the idea is that if one date is greater than the other it puts in a field the word "Before" or "After" if it's lower.
I've tried doing this but failed miserably.
For col = 0 To objSheet.UsedRange.Rows.Count
objSheet.Range("A2").Formula = "=IF(Value < B&(col+2), Before,After)"
Next
Line objSheet.Range("A2").Formula = "=IF(Value < B&(col+2), Before,After)" seems to not throw any error but all I get in my excel file is #NAME?
Value is a string that contains the value to which the other string is going to be compared to. Example
' Value = 5/4/2016 8:00:00 PM
' Value in B column: 5/5/2016 12:00:00 PM
then the if should output = BEFORE but if
'Value in B column: 5/5/2016 2:00:00 AM
then the output should be AFTER
I've seen some diff_date function but it doesn't seem to be integrated because it doesn't show up as an option.
It's there a way to achieve this? What am I doing wrong?
Edited This works for me in Excel 2013, but I've had to make some assumptions about what you're doing. The below code fills in column A depending on whether the date in Value is before or after the value in column B.
Option Explicit
Option Base 0
Public Sub PopulateDates()
Dim dateString As String
Dim row As Long
dateString = "5/4/2016 8:00:00 PM"
Dim dateRepresentation As String
dateRepresentation = "VALUE(""" & dateString & """)"
Dim newFormula As String
For row = 1 To ActiveSheet.UsedRange.Rows.Count
newFormula = "=IF(" & dateRepresentation & " < B" & CStr(row) & ", ""Before"",""After"")"
ActiveSheet.Cells(row, 1).Formula = newFormula
Next row
End Sub
Lots going on here.
dateString instead of Value per Scott Craner's note, and because Value is so generic you might forget what it means.
dateRepresentation is a way to represent a date as a literal in a worksheet formula. It is, e.g., VALUE("5/4/2016 8:00 PM").
newFormula is on its own line so you can check it in the debugger before you make the assignment. :)
the row (you had col+2) is part of your VBA, so it is not part of your formula. CStr(row) makes a string (e.g., 42), and then & pastes that string after B and before ,.
ActiveSheet.Cells instead of ActiveSheet.Range because the latter didn't work for me. You would use objSheet instead of ActiveSheet
The result of all this is to put, e.g., in cell A1:
=IF(VALUE("5/4/2016 8:00:00 PM") < B1, "Before","After")

Excel-VBA get number from letter equivalent entry in a textbox

I have a user form that asks for a column reference to be entered into a TextBox. I am trying to make it so that the number replaces the letter in the code. I am using the Cells(a,b) format to do this. Which is why I need the number to replace the letter.
Worksheets("AIO").Cells(X, TextBox3).Value = Worksheets("FDSA").Cells(Y, TextBox4).Value
Hence in the previous code when: x=2, TextBox3.Value=A, y=4, and TextBox4.Value=AA
The code will work as
Worksheets("AIO").Cells(2, 1).Value = Worksheets("FDSA").Cells(4, 27).Value
The only thing I can think of making is a huge if statement were I code something similar like this:
If textbox3.value ="A" then
textbox3.value=1
elseif textbox3.value=E then
textbox3.value=5
.......
.......
textbox3.value=AD then
textbox3.value=30
End If
If you want to keep it uncomplicated, .Cells can use the column string instead of number
Sub test()
'From sheet module
Debug.Print Me.Cells(1, 1).Address 'prints $A$1
Debug.Print Me.Cells(1, "A").Address 'also prints $A$1
End Sub
You might have to validate that it's a valid column ID but you probably need to do that even converting it to a number.
On way to get a column number from a character string:
Sub ColumnNumber()
Dim s As String
Dim L As Long
s = "AB"
L = Cells(1, s).Column
MsgBox L
End Sub

Convert Negative Time Stored as Text

I need to be able to convert time values stored as text to number. Those that have been stored as text appear to be negative values, in this format -0:03. I need to be able to use these numbers in calculations.
Can someone advise how to fix as nothing seems to work.
Select the cells that contain the negative text time values and run this small macro:
Sub ConvertNegativeTime()
Dim r As Range, v As String
Dim t As Date
For Each r In Selection
With r
v = .Text
If Left(v, 1) = "-" Then
ary = Split(Mid(v, 2), ":")
t = TimeSerial(ary(0), ary(1), 0)
.Clear
.Value = -CDbl(t)
End If
End With
Next r
End Sub
If A1 contains your posted value, after running the macro it would contain:
-0.020833333

Checking Data Types in a Range

I am trying to validate the data types of all cells in a user-selected range are the same, using a VBA function. I have the following code (simplified), which works for the most part:
Dim vTempRange As Variant
Dim vCell As Variant
vTempRange = DataRange.Value
For Each vCell In vTempRange
If Len(vCell) > 0 Then
'Use TypeName(vCell)
'Complete validation here
End If
Next vCell
Sometimes a user may select a column of percentages, sometimes a column of decimal values, and sometimes a time value (not associated with a date). VBA seems to see all three of these as a Double, which is technically not incorrect. The problem is, the format of the selection will be used as part of the final output, so 12:00:00 should display as such, and not 0.50, which is currently the case.
I looked into using something like this in conjunction:
Dim vCell As Variant
For Each vCell In DataRange
If Len(vCell) > 0 Then
'Use vCell.NumberFormat
'Complete validation here
End If
Next vCell
But the NumberFormat is not consistent. e.g., a user may have a percent listed as 0% vs. 0.000% or a time as h:m:s vs. hh:mm:ss, so I see it as being difficult to correctly capture this value.
Is there a way to accurately determine without user intervention when a time is selected vs. one of the other types? Determining a percent value versus a 0<x<1 decimal value would also be nice, but not required.
I have other options at my disposal, such as ignoring the formatting in the final output (really not desirable) or explicitly asking the user to identify the type (but this is neither as clean nor automatic as I would like).
Try this. Paste this in a module. You can then use it as a Worksheet formula.
I had this code in my database which was picked up from here and I modified it to suit your needs.
Public Function CellType(c)
Application.Volatile
Select Case True
Case IsEmpty(c): CellType = "Blank"
Case Application.IsText(c): CellType = "Text"
Case Application.IsLogical(c): CellType = "Logical"
Case Application.IsErr(c): CellType = "Error"
Case IsDate(c): CellType = "Date"
Case InStr(1, c.Text, ":") <> 0: CellType = "Time"
Case InStr(1, c.Text, "%") <> 0: CellType = "Percentage"
Case IsNumeric(c): CellType = "Value"
End Select
End Function
ScreenShot
You can further modify it to add an IF clause inside Case IsNumeric(c): CellType = "Value" to check for decimals, Scientific notation etc using INSTR
Declare vCell as Range and then do your check:
TypeName(vCell.Value)
That will accurately catch your dates.
YOu will likely need to add some if/then logic to capture "percents" since these are double-type values -- the "%" part is merely cell formatting, so you may be able to just check the Right(vCell.NumberFormat,1) = "%" .
VarType function
Returns an Integer indicating the subtype of a variable, or the type of an object's default property.
https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/vartype-function
Ex1: using to write if.
Function DataType(x As Variant) As String
If VarType(x) = vbDate Then
DataType = "Date"
ElseIf VarType(x) = vbString Then
DataType = "String"
'.....
End If
End Function
Ex2: concatenate cells in range having value.
Function ConcatenateRange(cellRange As Range) As String
Dim cel As Range, temp As String
temp = ""
For Each cel In cellRange
'use VarType to check if the cell is empty.
'if the cell is not empty concatinate it.
If VarType(cel) <> vbEmpty Then
temp = temp & cel
End If
Next
ConcatenateRange = temp
End Function