VBA Excel - Equal dates do not evaluate as equal - excel

MS Excel Professional Plus v14 on Win7.
I am having trouble comparing equality for date/times.
Two dates that appear to be equal, 12/16/2013 12:19:33 pm are both dimensioned as dates. One is in a date array, the other is a date variable. arrPP is ReDim'ed later. When I do DateDiff("s",date1,date2) it yields 0.
Dim arrPP() As Date ' During runtime shows type is Date(1 to 2, 1 to 1)
Dim dNextStartTime as Date
'...code removed ...
If arrPP(iPP_START, lPP_index) <= dNextStartTime Then
GoTo PP_OUT
End If
Even though they are equal, the above evaluates to false and the wrong path is taken. This was hard to track down and causes unexpected/wrong results.
Is there an offical "gotcha" regarding date equality? Are there hidden milliseconds that need to be compared, or a way to limit the comparison down to the seconds level?
I have tried several other alternatives including placing CDate in front of the array element.
FAIL:
If Not(arrPP(iPP_START, lPP_index) > dNextStartTime) Then
GoTo PP_OUT
End If
PASS: (But who would think to do this?)
If arrPP(iPP_START, lPP_index) <= dNextStartTime Or _
DateDiff("s",arrPP(iPP_START,lPP_index),dNextStartTime) = 0 Then
GoTo PP_OUT
End If

This is most likely due to floating point precission issues. Dates are stored as double precission floats, where the integer part is date and fractional part is time.
To test if arrPP(iPP_START,lPP_index) is before dNextStartTime it's probably best to use
If DateDiff("s",dNextStartTime,arrPP(iPP_START,lPP_index)) <= 0 Then
Note that DateDiff returns possitive when the first date parameter is earlier than the second.
To demonstrate how two apparently equal dates may not be equal, try running this
Sub demo()
Dim d1 As Date, d2 As Date
d1 = #12/17/1986 12:19:33 PM#
d2 = #12/17/1986#
d2 = d2 + 12# / 24# ' Add 12 hour
d2 = d2 + 19# / 60# / 24# ' Add 19 minutes
d2 = d2 + 33# / 60# / 60# / 24# ' Add 33 seconds
Debug.Print d1; d2
Debug.Print d1 = d2
Debug.Print d1 - d2
End Sub
Immediate window output
17/12/1986 12:19:33 p.m. 17/12/1986 12:19:33 p.m.
False
3.63797880709171E-12

VBA Excel - Equal dates do not evaluate as equal
It works for me.
I guess it boils down to how are you storing the date in the date variable or the date array. How are you populating the dates?
Here is the test that I did. Let me know if I have misunderstood your query.
Sub Sample()
Dim dt As Date
Dim MyAr(1, 1) As Date
dt = #12/16/2013 12:19:33 PM#
MyAr(1, 1) = #12/16/2013 12:19:33 PM#
If (MyAr(1, 1) > dt) Then
MsgBox "MyAr is greater"
ElseIf (MyAr(1, 1) < dt) Then
MsgBox "MyAr is lesser"
Else
MsgBox "They are equal" '<~~ This is what I get
Debug.Print DateDiff("s", MyAr(1, 1), dt)
End If
End Sub

Most likely you don't need an answer after so many years but if anybody will join this question maybe it will be useful.
Function DateEqual(date1 As Date, date2 As Date) As Boolean
'use should never compare as equal dates or any double values but if you really need to do it carefully use this function.
'converting to integers to test if they are equal. if you need to test time values recorded with less then 1second DO NOT USE THIS FUNCTION.
Dim day1, day2 As Date
Dim time1, time2 As Date
day1 = Int(date1)
day2 = Int(date2)
If day1 = day2 Then
If Hour(date1) = Hour(date2) And Minute(date1) = Minute(date2) And Second(date1) = Second(date2) Then
DateEqual = True: Exit Function
Else
DateEqual = False: Exit Function
End If
Else
DateEqual = False: Exit Function
End If
End Function

Related

How can I force a NumberFormat on a VBA function result from the VBA function?

Trying to convert timestamps that are given in seconds after Jan first 1970. I have written a small function in Excel VBA to convert to the Excel date format. This works fine in the sense that it converts to a number that if formatted correctly gives the timestamp in an intelligible way, but I have to format the calls by hand each time. I have tried to address the issue in several ways, but either it does not do anything to the number, or it results in an error: "#VALUE." The function is called Sec2TS and if I use: 1502569847 as input it returns: 42959.8547106481, which is correct, but I would like to see: 2017 Aug 12 20:30:47. I have added the code:
Function Sec2TS(Secs As Double) As Date
If Secs > 0 Then
Sec2TS = 25569 + (Secs / 86400)
Else
Sec2TS = 0
End If
ActiveCell.NumberFormat = "yyyy mmm dd hh:mm:ss"
End Function
What is wrong with this? I have tried with set range to selection and toggling application, but to no avail.
If a formula could change formattings on a sheet, that would result in totally crazy effects for all users, because they would not know why all these odd things actually happen. That is probably the main reason why a formula/UDF cannot change anything in a worksheet, it can only return a value.
As workaround you can use the Worksheet_Change event to format the cell right after you entered a formula that contains Sec2TS. So first we check wich cells of the changed range (Target) contain formulas (Target.SpecialCells(xlCellTypeFormulas)) and then check if any cell in this range contains "Sec2TS" in its formula to .NumberFormat this cells.
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim CellsWithFormulas As Range
On Error Resume Next
If Target.Cells.CountLarge > 1 Then
Set CellsWithFormulas = Target.SpecialCells(xlCellTypeFormulas)
ElseIf Target.HasFormula Then
Set CellsWithFormulas = Target
End If
On Error GoTo 0
If CellsWithFormulas Is Nothing Then Exit Sub
Dim Cell As Range
For Each Cell In CellsWithFormulas.Cells
If InStr(1, Cell.Formula, "Sec2TS") > 0 Then
Cell.NumberFormat = "yyyy mmm dd hh:mm:ss"
End If
Next Cell
End Sub
Note that the Target.Cells.CountLarge > 1 check is needed because if you apply SpecialCells to only one single cell VBA will apply it automatically to all cells of the worksheet which makes the code very slow.
If you don't need to process the values numerically, you can use String rather than Date as the function output:
Function Sec2TS(Secs As Double) As String
Dim D As Double
If Secs > 0 Then
D = CStr(25569# + (Secs / 86400#))
Else
D = CStr(0)
End If
Sec2TS = Format(D, "yyyy mmm dd hh:mm:ss")
End Function

Check for valid date - VBA

Guys my primary objective is to avoid invalid days.
In sheet 1 i have:
A1 data validation with years (from 1900-2019)
B1 data validation with all months
C1 i use change event (if both fields A1 & A2 are not empty) calculate how many days the selected month has based on the selected year and create a data validation includes all available days.
For days calculation i use:
Option Explicit
Sub test()
Dim ndays As Long
With ThisWorkbook.Worksheets("Sheet1")
ndays = Day(DateSerial(.Range("A1").Value, .Range("B1").Value + 1, 1) - 1)
End With
End Sub
Sheet Structure:
Is there a batter way to calculate days?
you could use:
DateValue() function to build a date out of a string you compose with your year and month values and adding any valid day number (I chose "1" to be sure...)
EOMONTH() worksheet function to get the last day of the resulting date month:
like follows:
With someSheet
...
nb_days = Day(WorksheetFunction.EoMonth(DateValue(.Range("A1").Value & " " & .Range("B1").Value & " 1"), 0))
...
End With
I suggest to use the UDF (User Defined Function) below.
Function MonthDays(Rng As Range) As Integer
Const Y As Integer = 1
Const M As Integer = 2
Dim Arr As Variant
Application.Volatile ' recalculates on every change
If Application.WorksheetFunction.Count(Rng) = 2 Then
Arr = Rng.Value
MonthDays = DateDiff("d", DateSerial(Arr(Y, 1), Arr(M, 1), 1), _
DateSerial(Arr(Y, 1), Arr(M, 1) + 1, 1))
End If
End Function
You can call it directly from the worksheet with a function call like =MonthDays(A1:A2) where A1 holds the year and A2 holds the month. If either is missing the function returns 0. The function accepts impossible numbers for both year and month and will return a logical result, such as the 14th month of a year being the following year's February. However, you can limit the entries by data validation.
All UDFs can be called as normal functions from your code. Cells(3, 1).Value = MonthDays(Range("A1:A2")) would have the same effect as entering the function call as described in the preceding paragraph in A3. However, if the function is called from VBA the line Application.Volatile would be not required (ineffective).

Why is my created function returning an error?

I have created a function which has 4 parameters: SearchDate, StartDate, EndDate, Events. The way I wanted the function to work was if the SearchDate is >= for some start date and =< for some end date then the function pulls the events name. For example, if the search was June 17 and the start/end date was June 15/June 18 then it would pull the event.
However the code doesn't seem to work; when I try to use it gives me a value error. I have posted the code and a table, that the function is based on, below.
Function Calendar_Events(SearchDate As Date, StartColumn As Range, EndColumn As Range, EventsColumn As Range)
Dim x As Long
Dim output As Range
For x = 1 To StartColumn.Cells.CountLarge
If Int(StartColumn.Cells(x)) <= SearchDate And Int(EndColumn.Cells(x)) >= SearchDate Then
'in place for the case of more events then rows
If y >= 3 Then
output = output & "........"
Exit For
End If
output = output & Left(EventsColumn.Cells(x), 20) & vbNewLine
y = y + 1
End If
Next x
End Function
Table:
Start Date End Date Event
1/12/2018 1/19/2018 Software Sale
1/31/2018 1/31/2018 Dinner Party
2/1/2018 2/1/2018 Baby Shower
2/12/2018 2/16/2018 Team Retreat
2/15/2018 2/16/2018 Bank Meetings
2/15/2018 2/15/2018 Lunch Date
2/15/2018 2/15/2018 Dinner Date
3/26/2018 3/29/2018 Vacation
3/28/2018 3/29/2018 Swimming
3/28/2018 3/28/2018 Mountain Biking
3/29/2018 3/29/2018 Put away clothes
3/29/2018 4/4/2018 Cottage
4/2/2018 4/2/2018 Family Photo
4/2/2018 4/4/2018 Software Sale
4/2/2018 4/6/2018 Hire Nanny
4/6/2018 4/6/2018 Day Off
1. In order to return a value from a Function you must set the Function name equal to what you want to return.
So at the end of your code you need:
Calendar_Events = output
So it knows to return the output variable you've been building.
2. Furthermore your output variable should be String. You are not collecting Ranges here, but rather the values inside of cells that match your criteria, so:
Dim Output As String
3. Also, there is no need to convert the cell values containing dates to integers. You are comparing dates to dates and that is good to go without converting. so:
If StartColumn.Cells(x).Value <= SearchDate And EndColumn.Cells(x).Value >= SearchDate Then
I've also added .value to the end of the Cell() reference. It will default to the .value property of the cell, but I'm a big fan of explicit coding instead of just hoping the compiler will know which property you meant.
4. Lastly (and optionally) you should declare the TYPE of the return from the function in the function definition. so:
Function Calendar_Events(SearchDate As Date, StartColumn As Range, EndColumn As Range, EventsColumn As Range) As String
All of this together:
Function Calendar_Events(SearchDate As Date, StartColumn As Range, EndColumn As Range, EventsColumn As Range) As String
Dim x As Long
Dim output As String
For x = 1 To StartColumn.Cells.CountLarge
Debug.Print StartColumn.Cells(x).Value, EndColumn.Cells(x).Value
If StartColumn.Cells(x).Value <= SearchDate And EndColumn.Cells(x).Value >= SearchDate Then
'in place for the case of more events then rows
If y >= 3 Then
output = output & "........"
Exit For
End If
output = output & Left(EventsColumn.Cells(x), 20) & vbNewLine
y = y + 1
End If
Next x
Calendar_Events = output
End Function

Excel Formula - Comparing Range of Date

how to write EXCEL VBA to make my date from 04/JUNE > 04/JUNE 23:59. i cannot use the '= date + 5/6' as i have to run the VBA many times a day and it will add my dates to tomorrow/ the day after tomorrow. i just wanna make the date to end of date. please help.
The example will be like
CELL A1 : 12/June
CELL B1 : 15/June
CELL C1 : 15/June 12:00 HRS
CELL D1 : =IF C1B2, “OUT OF RANGE”, “Okay !”)
in this case D1 will still display OUT OF RANGE.
I have loads of such to change so I was thinking of writing a VBA to automatically convert C1 from 15/JUNE -> 15/JUNE 23:59 , so that D1 will display Okay !
I tried Cdate(Range(“D1”)) + 5/6 in vba to make it 23:00 hrs and I run this macro a few times in a day and it will keep adding 23hrs to the date and made it change to another date.
About what you are talking?
About:
- "Excel Formula" or
- "EXCEL VBA"
?
About:
- "Comparing Range of Date" or
- "to make my date"
?
Assume, that VBA is...
The Date type in VBA is Double, where:
- Integer part as quantity of days from... (look F1), and
- Fractional part of day.
So, your desired "04/JUNE > 04/JUNE 23:59" will:
? DateSerial(2018, 06, 4) + ((23& * 60& * 60&) + (59& * 60&)) / 86400&
18-06-04 23:59:00
Yeah, sure, you can use TimeSerialinstead of above, but ... it didn't give you right understanding of VBA dates.
.
----------
ADD:
Sorry but will this code only work for one cell because I have lots of
cells with dates and I need VBA to extract them out and convert to
23:59 for eg I run a for loop to change like 20 cells in a row with
multiple range. And I will run the macro a few times in a day will it
add 23hrs to that date every time and cause it to change dates ?
Public Sub sp_Test_Date()
Dim rSel As Range
Dim i&
Const H23M59 As Double = 1 - 60 / 86400
Set rSel = Selection
With rSel
For i = 1 To .Cells.Count
With .Cells(i)
If IsDate(.Value) Then
.Offset(0, 1).Value = CDate(Int(.Value) + H23M59)
End If
End With ' .Cells(i)
Next
End With ' rSel
End Sub
i tried modifying it to below and it failed me :(
`
Dim x As Integer
Dim test1 As Date
Dim schdWS, compWS As Worksheet
Const H23M59 As Double = 1 - 60 / 86400
Set schdWS = Sheet1
Set compWS = Sheet11
lastrow = schdWS.Cells(Rows.Count, 8).End(xlUp).Row
For x = 20 To lastrow
If IsDate(schdWS.Cells(x, 3).Value) Then
Cells(x, 3).Value = CDate(Int(Cells(x, 3).Value + H23M59))
End If
'test1.NumberFormat = "dd/mm/yyyy"
'schdWS.Cells(x, 3).FormulaR1C1 = test1 & " 23:59"
Next x
End Sub
`

Working with dates in Visual Basic / Excel

Very new to working with Visual Basic / Excel. I am trying to write a quick script that enters the current time in one column, and allows the user to enter how many days/hours/minutes will pass until a new time, and output that in another column.
I'm sure this isn't the best way to do it, but what I have so far is the following. I have given up on fiddling with dates, and am just working with the time:
Sub TimeModule()
Dim DaysLeft, HoursLeft, MinutesLeft As Double
DaysLeft = Val(InputBox("Days left"))
HoursLeft = Val(InputBox("Hours left"))
MinutesLeft = Val(InputBox("Minutes left"))
Dim CurrentTime As Date
CurrentTime = TimeValue(Now())
ActiveCell.Value = CurrentTime
ActiveCell.Offset(0, 1) = CurrentTime + Time(HoursLeft, MinutesLeft, 0)
End Sub
I am getting an error, of course. If anyone could shed some light on a better way to do this, along with the functions I'm misusing, I would really appreciate it!
Edit: I would, of course ultimately like for the script to handle days as well.
I think this is possible just using cell functions in Excel, if I've understood you correctly.
For example, this is what you'd see...
Time Now: Days: Hours: Minutes: New Time:
30/05/2012 23:34 15 6 23 15/06/2012 05:57
...and this is what is in each cell (assuming top-left cell is A1)...
Time Now: Days: Hours: Minutes: New Time:
=NOW() 15 6 23 =A2+B2+TIME(C2,D2,0)
Describing each function:
NOW() returns the current date and time formatted as a date and time.
DATE(year,month,day) returns the number that represents the date in MS Excel date-time code.
TIME(hours,minutes,seconds) converts hours, minutes, and seconds given as numbers to an Excel serial number, formatted with a time format.
Dissecting the equation in the last cell:
A2 is the cell containing the current date/time (as of last worksheet calculation).
B2 is the user-inputted value for days.
TIME(C2,D2,0) is the TIME() function, taking the user-inputted values for hours and minutes from cells C2 and D2 respectively.
Is this anything like your intended functionality...?
If you want to use VBA the only issue with your code is the "Time" function.
You can use CDate instead :
Sub TimeModule()
Dim DaysLeft, HoursLeft, MinutesLeft As Double
DaysLeft = Val(InputBox("Days left"))
HoursLeft = Val(InputBox("Hours left"))
MinutesLeft = Val(InputBox("Minutes left"))
Dim CurrentTime As Date
CurrentTime = TimeValue(Now())
ActiveCell.Value = Now()
ActiveCell.Offset(0, 1) = ActiveCell.Value + DaysLeft + CDate(HoursLeft & ":" & MinutesLeft)
'ActiveCell.Offset(0, 1) = CurrentTime + Time(HoursLeft, MinutesLeft, 0)
End Sub
When you 'Dim' in that fashion, you have to record the data type for each variable. The way you have it MinutesLeft is a Double and everything is (by default) a Variant.
The Time function you're looking for is TimeSerial.
Dates are stored as the number of days since a certain date. To add days to a date, you can simply add the numbers together.
Sub TimeModule()
Dim lDaysLeft As Long
Dim lHoursLeft As Long
Dim lMinutesLeft As Double
Dim dtCurrent As Date
lDaysLeft = Val(InputBox("Days left"))
lHoursLeft = Val(InputBox("Hours left"))
lMinutesLeft = Val(InputBox("Minutes left"))
dtCurrent = Now()
ActiveCell.Value = dtCurrent
ActiveCell.Offset(0, 1).Value = dtCurrent + lDaysLeft + TimeSerial(lHoursLeft, lMinutesLeft, 0)
End Sub

Resources