How to make match() work with date in excel vba? - excel

I'm having problem making the match() work in excel VBA. The code is:
x = Application.Match("Sep 2008", Range("F1:F1"), 0)
The value in cell F1 is 9/1/2008.
Even if I changed Sep 2008 to 9/1/2008, it still doesn't return any value.
Any idea how to fix it?

The reason why Even if I changed Sep 2008 to 9/1/2008, it still doesn't return any value.
Is because when there is a Date in excel, Excel automatically converts that date to a numeric value, What you really want to search for is:
39692
This number is the number of days between 9/1/2008 and excel default of 1/1/1900
every date in excel is stored with a value like this. So the easiest way to handle this would be to convert what you see as a date to what excel sees as a date using CDate().
This by itself will give you an unuseful error that vba can't get the property.
That is because the Lookup_value can be a value (number, text, or logical value) or a cell reference to a number, text, or logical value. Not a date so simply convert the now date value to a number to search for the matching number in the list using CLng()
Give this a shot it will also be much faster then using the Find alternative:
x = WorksheetFunction.Match(CLng(CDate("Sep 2008")), Range("F1:F1"), 0)
This should give you the result expected
To handle when no match is found try this Sub:
Sub MatchDate()
Dim myvalue As Double
Dim LastRow As Long
LastRow = Cells(Rows.Count, "F").End(xlUp)
On Error GoTo NotFound
myvalue = WorksheetFunction.Match(CLng(CDate("Sep 2008")), Range("F1:F" & LastRow), 0)
MsgBox (myvalue)
End
NotFound:
MsgBox ("No Match Was Found")
End
End:
End Sub

Your best bet is to use .Find(). This will return a range if found or nothing if not.
Set x = Range("F1:F1").Find(CDate("Sept 2008"), , , xlWhole)
If you wanted the column number:
x = Range("F1:F1").Find(CDate("Sept 2008"), , , xlWhole).Column
With capture of not found
Sub test()
Dim y As Date, x As Variant, c As Long
y = CDate("Sep 2008")
Set x = Range("1:1").Find(y, , , xlWhole)
If Not x Is Nothing Then
c = x.Column '<~~found
Else
Exit Sub 'not found
End If
End Sub

Bottom line:
use WorksheetFunction.Match(CDbl(date), range, 0)
Alternatively, use a Date cell's Value2 property (which will also be a Double) instead of Value for the search key.
CLng suggested in other answers would discard the time part of the date.
The same problem exists for the Currency data type but you can't use CDbl for it (see below for options).
Range.Value2 Property (Excel) article suggests that Date and Currency types are "special" in that they have an "internal representation" that's in stark contrast with displayed value. Indeed:
Date is internally represented as IEEE 64-bit (8-byte) floating-point numbers where the integer part is the date and fractional part is the time
Currency is also 8-byte but is treated as a fixed-point number with 4 fractional digits (an integer scaled by 10'000)
Apparently, Match compares these internal values for performance reasons. So, we must ensure that they, rather than the readable representations, match exactly.
Since Date is already floating-point internally, CDbl(date) doesn't actually change the data.
For the Currency type, CDbl does change data, so it's out of question. So either
use the exact representation of the key (to 4 fractional digits) this way or another if you require exact match, or
make the cells in the range actually be formulas with Round) if the value to compare with comes from elsewhere and/or you only require equality to 2 fractional digits

This way it works using this method:
Nbr,L, C as Integer
Datedeb as date
nbr = WorksheetFunction.Match(CLng(CDate(Datedeb)), Range(Cells(L, C), Cells(L + 100, C)), 0)

I think I can safely assume that the value in F1 is a date. In you code "Sep 2008" is a string. You will never be able to get a successful match as long as your datatypes are inconsistent.
If you are looking for a date, then make sure that the first parameter is a date.
Dim dSearchSDate As Date
dSearchSDate = "01/Sept/2008"
x = Application.Match(dSearchSDate, Range("F1:F1"), 0)
Here is another possible approach.
Sub temp()
Dim x
Dim dSearchSDate As Date
dSearchSDate = "01/Sept/2008"
If ThisWorkbook.Worksheets(1).Range("F1:F1").Value = dSearchSDate Then
Debug.Print "Found it!"
Else
Debug.Print "Doh!!"
End If
End Sub

I know this post is old, but I had the same issue, and did find the answer.
To make it work, you first need to make VBA see the same data formatting as it appears in your excel spreadsheet :
YourVar = Format("YourDate","mmm-yyyy")
YourResult = Application.match(Clng(Cdate(YourVar)), YourRange, 0)
Regards
Gilles

Related

Summing the digits in Excel cells (long and short strings)

I'm working on a research related to frequencies.
I want to sum all the numbers in each cell and reduce them to single number only.
some cells have 2 numbers, others have 13 numbers. like these..
24.0542653897891
25.4846064424057
27
28.6055035477009
I tried several formulas to do that. the best ones have me 2 digits number, that I couldn't sum it again to get a single result.
like these Formulas:
=SUMPRODUCT(MID(SUBSTITUTE(B5,".",""),ROW(INDIRECT("1:"&LEN(B5)-1)),1)+0)
=SUMPRODUCT(1*MID(C5,ROW(INDIRECT("1:"&LEN(C5))),1))
any suggestion?
Thank you in advance.
EDIT
Based on your explanation your comments, it seems that what you want is what is called the digital root of the all the digits (excluding the decimal point). In other words, repeatedly summing the digits until you get to a single digit.
That can be calculated by a simpler formula than adding up the digits.
=1+(SUBSTITUTE(B5,".","")-1)-(INT((SUBSTITUTE(B5,".","")-1)/9)*9)
For long numbers, we can split the number in half and process each half. eg:
=1+MOD(1+MOD(LEFT(SUBSTITUTE(B5,".",""),INT(LEN(SUBSTITUTE(B5,".",""))/2))-1,9)+1+MOD(RIGHT(SUBSTITUTE(B5,".",""),LEN(SUBSTITUTE(B5,".",""))-INT(LEN(SUBSTITUTE(B5,".",""))/2))-1,9)-1,9)
However, the numbers should be stored as TEXT. When numbers are stored as numbers, what we see may not necessarily be what is stored there, and what the formula (as well as the UDF) will process.
The long formula version will correct all the errors on your worksheet EXCEPT for B104. B104 appears to have the value 5226.9332653096000 but Excel is really storing the value 5226.9333265309688. Because of Excel's precision limitations, this will get processed as 5226.93332653097. Hence there will be a disagreement.
Another method that should work would be to round all of the results in your column B to 15 digits (eg: . Combining that with using the long formula version should result in agreement for all the values you show.
Explanation
if a number is divisible by 9, its digital root will be 9, otherwise, the digital root will be n MOD 9
The general formula would be: =1+Mod(n-1,9)
In your case, since we are dealing with numbers larger than can be calculated using the MOD function, we need to both remove the dot, and also use the equivalent of mod which is n-(int(n/9)*9)
Notes:
this will work best with numbers stored as text. Since Excel may display and/or convert large numbers, or numbers with many decimal places, differently than expected, working with text strings of digits is the most stable method.
this method will not work reliably with numbers > 15 digits.
If you have numbers > 15 digits, then I suggest a VBA User Defined Function:
Option Explicit
Function digitalRoot(num As String) As Long
Dim S As String, Sum As Long, I As Long
S = num
Do While Len(S) > 1
Sum = 0
For I = 1 To Len(S)
Sum = Sum + Val(Mid(S, I, 1))
Next I
S = Trim(Str(Sum))
Loop
digitalRoot = CLng(S)
End Function
You could use a formula like:
=SUMPRODUCT(FILTERXML("<t><s>"&SUBSTITUTE(A1," ","</s><s>")&"</s></t>","//s"))
You might need an extra SUBSTITUTE for changing . to , if that's your decimal delimiter:
=SUMPRODUCT(FILTERXML("<t><s>"&SUBSTITUTE(SUBSTITUTE(A1,".",",")," ","</s><s>")&"</s></t>","//s"))
However, maybe a UDF as others proposed is also a possibility for you. Though, something tells me I might have misinterpreted your question...
I hope you are looking for something like following UDF.
Function SumToOneDigit(myNumber)
Dim temp: temp = 0
CalcLoop:
For i = 1 To Len(myNumber)
If IsNumeric(Mid(myNumber, i, 1)) Then temp = temp + Mid(myNumber, i, 1)
Next
If Len(temp) > 1 Then
myNumber = temp
temp = 0
GoTo CalcLoop
End If
SumToOneDigit = temp
End Function
UDF (User Defined Functions) are codes in VBA (visual basic for applications).
When you can not make calculations with Given Excel functions like ones in your question, you can UDFs in VBA module in Excel. See this link for UDF .. If you dont have developer tab see this link ,, Add a module in VBA in by right clicking on the workbook and paste the above code in that module. Remember, this code remains in this workbook only. So, if you want to use this UDF in some other file your will have to add module in that file and paste the code in there as well. If you are frequently using such an UDF, better to make add-in out of it like this link
In addition to using "Text to Columns" as a one-off conversion, this is relatively easy to do in VBA, by creating a user function that accepts the data as a string, splits it into an array separated by spaces, and then loops the elements to add them up.
Add the following VBA code to a new module:
Function fSumData(strData As String) As Double
On Error GoTo E_Handle
Dim aData() As String
Dim lngLoop1 As Long
aData = Split(strData, " ")
For lngLoop1 = LBound(aData) To UBound(aData)
fSumData = fSumData + CDbl(aData(lngLoop1))
Next lngLoop1
fExit:
On Error Resume Next
Exit Function
E_Handle:
MsgBox Err.Description & vbCrLf & vbCrLf & "fSumData", vbOKOnly + vbCritical, "Error: " & Err.Number
Resume fExit
End Function
Then enter this into a cell in the Excel worksheet:
=fSumData(A1)
Regards,
The UDF below will return the sum of all numbers in a cell passed to it as an argument.
Function SumCell(Cell As Range) As Double
Dim Fun As Double ' function return value
Dim Sp() As String ' helper array
Dim i As Integer ' index to helper array
Sp = Split(Cell.Cells(1).Value)
For i = 0 To UBound(Sp)
Fun = Fun + Val(Sp(i))
Next i
SumCell = Fun
End Function
Install the function in a standard code module, created with a name like Module1. Call it from the worksheet with syntax like =SumCell(A2) where A2 is the cell that contains the numbers to be summed up. Copy down as you would a built-in function.

VBA Strings without Dollar Signs

I'm learning about VBA, and I've noticed an odd quirk when VBA interacts with adding strings. When you take the two versions of code (Change the commented parts with the swap here commented parts) one outputs a string with a dollar sign, and one does without. Does anybody know if this is a bug or is planned to be upgraded?
Option Explicit
Sub CalcCost()
Dim curSalesPrice As Currency
Dim curTotalCost As Currency
Dim sngSalesTax As Single
Dim strMessage As String
curSalesPrice = 35
sngSalesTax = 0.085
Range("A1:B8").ClearContents
Range("A1").Value = "The cost of the calculator"
Range("A4").Value = "Price"
Range("B4").Value = curSalesPrice
Range("A5").Value = "SalesTax"
Range("A6").Value = "Cost"
Range("B5").Value = curSalesPrice * sngSalesTax
'curTotalCost = curSalesPrice + (curSalesPrice * sngSalesTax)
curTotalCost = Format(curSalesPrice + (curSalesPrice * sngSalesTax), "Currency") 'swap here
'strMessage = "The calculator total is " & Format(curTotalCost, "Currency")
strMessage = "The calculator total is " & curTotalCost 'swap here
Range("A8").Value = strMessage
Range("B6").Value = curTotalCost
End Sub
Format is a VBA standard library function defined in the VBA.Strings module; it returns a String representation of the expression it's given, formatted as specified: it makes no sense to do this:
Dim foo As Currency ' a numeric data type...
foo = Format(123, "Currency") ' ...assigned to a string representation of a numeric value
But it makes complete sense here:
Dim msg As String
msg = Format(123, "Currency")
Now, the value of a cell is distinct from its text representation. It's not because you see $123.00 in a cell that the value of that cell is $123.00 (a String); that's the cell's Text, but its Value can very well be 123 (a Double) and its NumberFormat be $#0.00.
You want to use numeric data types to perform operations, and use Format only when you need to make these numeric values "pretty" for display. Avoid making arithmetic operations on strings: while that may work, it also may fail, depending on how the string is formatted, and the system locale: VBA needs to make implicit type conversions to carry out such operations, and implicit conversions need to make a number of (sometimes wrong) assumptions.
When writing numeric values to worksheet cells, write the numeric values, not a string representation of them (same for dates. especially dates, actually). Instead of Format-ing the values, specify a format string for Range.NumberFormat in the cells that need to be formatted. That way Excel will still understand the numeric values as such, and can still correctly perform e.g. SUM operations.
The code is working exactly as specified and intended.

VB.Net: Writing To Excel Workbook as Text

I have written the following function...
Public Function writeAUTLSheet(doc As XDocument, myNameSpaces As Dictionary(Of String, XNamespace), theSheet As Excel.Worksheet)
'Grab the number of the last row to not overwrite
Dim rowNumber As Integer = getLastRow(theSheet)
For Each myName As XElement In doc.Descendants.Elements(myNameSpaces("ns4") + "autl")
theSheet.Cells(rowNumber, 1) = doc.Descendants.Elements(myNameSpaces("ns") + "number").Value
theSheet.Cells(rowNumber, 2) = myName.Descendants(myNameSpaces("ns") + "id").Value
theSheet.Cells(rowNumber, 3) = myName.Descendants(myNameSpaces("ns") + "name").Value
rowNumber = rowNumber + 1
Next
End Function
It runs as expected, but some values are written as "General" and others are written as "Date". This leads to values such as 07-2-3018 being turned into 7/2/3018. Then if I change that cell to "Text" or "General"(manually) it turns into "408525".
Is there a way to specify that I want it to be written as text to achieve 07-2-3-18 being written?
When you put data that might be a string, or might be a date, Excel does wacky things with it, as you've observed, because it has no way of knowing whether the data you've provided should be one type or another.
Usually, anything that can be interpreted as a Date type, is interpreted as such. So, 1/1/2017 would be a valid date but 13/47/5047 cannot be, so Excel will treat the latter as a string literal, the former as a Date type. If a value is considered a Date, then it is also a Long numeric. The 408525 value is the long numeric representation of the Date value 7/2/3018.
There should be a NumberFormat property of your Cells object (which is an Excel.Range type), but the NumberFormat doesn't change the underlying value, and it's the interpretation of the value which is confusing Excel and causing it to represent date-like values as Dates.
You can always prepend the cell value with an apostrophe, which will force Excel to interpret as string, e.g.:
theSheet.Cells(rowNumber, 2) = "'" + myName.Descendants(myNameSpaces("ns") + "id").Value

VBA - Find date within text containing format 'DDMMYYYY'

I've scanned and found many similar answers to my question although none of them are quite ticking the boxes, so I'll explain what I'm after and hopefully it makes sense:
I have imported data into an excel spreadsheet (working with 2007) from a TXT, which contains various dates (all in the format DDMMYYYY).
What I'm trying to do is create a sub routine which finds any dates in this format and subsequently decreases the date by 1 year.
The dates are within a range when the TXT has been imported (in this instance, the first 2 dates appear under range A6) so ideally I would like to specify that range because I don't necessarily want to decrease all dates present in the TXT.
For example, here's the incorrect code which I am sure needs some serious tweaking!
Cells.Replace What:="Dates", Replacement:="Dates-1", LookAt:=xlPart, _
SearchOrder:=xlByRows, MatchCase:=False, SearchFormat:=DDMMYYYY, _
ReplaceFormat:=False
I gather there is some prior definition that needs to be carried out before anything like the above code would work but if there is anyone who can help, I'd appreciate it!
Many thanks,
Robin.
I do not believe any tweak will allow your code to achieve the effect you seek. I cannot give a definitive answer because I do not quite believe your description. I hope the information below will allow you to investigate your data and develop an appropriate solution.
You are not, as far as I know, using SearchFormat correctly. You need something like this:
With Application.FindFormat
.Font.Bold = True
End With
Set Rng = .Find(. . . SearchFormat = True . . .)
The VBA Editor’s Help for Find states: “SearchFormat Optional Variant. The search format.” but gives no example to show what that means. I cannot find an example of using the find format facility that is not in the above style. That is: you use FindFormat to specify the format(s) of interest and set SearchFormat to True to indicate that those formats are to be searched for.
You could try:
With Application.FindFormat
.NumberFormat = "DDMMYYYY"
End With
I have not tried this and I cannot find any documentation that explains what format types can be searched for. If this works, it will almost certainly be faster and easier than anything based on the following information.
For Excel to import a string within a text file as a Date, it must:
Recognise the string as a date.
Convert that string to a number.
Store that number with a number format that indicates it is a date.
Excel stores dates as the number of days since 1/1/1900. For example, “21 August 2015” will be stored as 42238 with a number format of “dd mmmm yyyy”. There is nothing about the cell value that says it’s a date. You can enter the cell value as 42238 and later set the number format to “dd mmm yy” and the value will display as “21 Aug 15”.
Your description implies the dates are held in the text file as eight-digit values which Excel recognises as dates and therefore sets the number format to "DDMMYYYY". I have never managed to get Excel to recognise eight-digit values as dates. If you have succeeded, I would like to know how.
My best suggestion is:
For Each CellCrnt in RngImport
If CellCrnt.NumberFormat = "ddmmyyy" Then
' Add year to date
End If
Next
Extension
If you perform the actions I requested in the comment against your question, we should be able to identify the type of the values you wish to locate and modify. This extension explains how you might use that information.
In my earlier code I used RngImport to represent the Range of the imported data. One of your remarks makes me wonder: do you know how to initialise that Range?
Do we have to search the entire Range for these “dates”? That is, are there scattered across the data or are they restricted to a single column? The final code will be faster if we do not have to examine every cell.
My guess is we will need something like:
If IsDate(Mid(CellValue,1,2) & "/" & Mid(CellValue,3,2) & "/" & Mid(CellValue,5,4)) Then
to convert “ddmmyyyy” to “dd/mm/yyyy” and then test the new string to be a date.
Here is my suggestion:
Sub OneYearEarlier()
Dim c As Range
Dim dt As Date
For Each c In Selection
If c.NumberFormatLocal = "TT.MM.JJJJ" Then
dt = DateAdd("yyyy", -1, c.Value)
c.Value = dt
End If
Next c
End Sub
Firstly, I think you have to handle the cell value as a Date data type, not as a string. This will avoid overflow or underflow which might occur if not decreasing by one year but some other time interval, e.g. one month.
Secondly, as mentioned by others you cannot reliably detect that a number is a date in XL. My code uses
- only cells which you have selected beforehand (e.g. one column)
- only cells with a date format
I use NumberFormatLocal here so that the format string is identical to the one which you see in the format dialog/"custom format". But this is not really critical here, just a convenience.
I really dislike having to use both the 'internal' format string "yyyy" and the localized format string "JJJJ" but that's the way DateAdd wants it.
You can always create your own RegEx function to simplify:
Function RegEx(Target As String, RegExpression As String, _
Optional ReplaceString As String, Optional xIgnoreCase As Boolean, _
Optional xGlobal As Boolean, Optional xMultiLine As Boolean)
Dim regexOne As Objectv
Set regexOne = New RegExp
regexOne.Pattern = RegExpression
If xIgnoreCase Then regexOne.IgnoreCase = xIgnoreCase
If xGlobal Then regexOne.Global = xGlobal
If xMultiLine Then regexOne.MultiLine = xMultiLine
If regexOne.Test(Target) Then
If IsMissing(ReplaceString) Then
RegEx = regexOne.Execute(Target)
Else
RegEx = regexOne.Replace(Target, ReplaceString)
End If
End If
End Function
You could use this for your problem in this way:
Function fDateAdd(SearchTextas Date, DateUnit as String, ModAmount as Interger)
FoundText = RegEx(SearchText, "######")
Do
If IsDate(FoundText) And (Mid(FoundText, 5, 2)="19" Or Mid(FoundText, 5, 2)="20") Then
ReplaceWithText = DateAdd(DateUnit, ModAmount, CDate(FoundText))
fDateAdd = Replace(SearchText, FoundText, ReplaceWithText)
End If
FoundText = RegEx(SearchText, "######")
Loop While FoundText <> ""
End Function

Turning an excel formula into a VBA function

I'm a bit new to trying to program and originally was just trying to improve a spreadsheet but it's gone beyond using a basic function in excel. I have a table that I am having a function look at to find a building number in the first column and then look at start and finish dates in two other respective columns to find out if it should populate specific blocks on a calendar worksheet. The problem occurs because the same building number may appear multiple times with different dates and I need to to find an entry that matches the correct dates.
I was able to create a working though complicated formula to find the first instance and learned I can add a nested if of that formula again in the false statement with a slight change. I can continue doing that but it becomes very large and cumbersome. I'm trying to find a way to make a function for the formula with a variable in it that would look at how many times the it has already been used so it keeps searching down the table for an answer that fits the parameters.
This is currently my formula:
=IFERROR(IF(AND(DATE('IF SHEET (2)'!$F$7,MATCH('IF SHEET (2)'!$C$2,'IF SHEET (2)'!$C$2:'IF SHEET (2)'!$N$2,0),'IF SHEET (2)'!C$4)>=VLOOKUP("2D11"&1,A2:F6,4,0),DATE('IF SHEET (2)'!$F$7,MATCH('IF SHEET (2)'!$C$2,'IF SHEET (2)'!$C$2:'IF SHEET (2)'!$N$2,0),'IF SHEET (2)'!C$4)<=VLOOKUP("2D11"&1,A2:F6,4,0)),IF(VLOOKUP("2D11"&1,A2:F6,3,0)="2D11",VLOOKUP("2D11"&1,A2:F6,6,FALSE)),"NO ANSWER"),"ERROR")
Where you see 2D11&1 is where I need the variable for 1 so it would be "number of times it's been used in the function +1" then I could just loop it so it would keep checking till it ran out of 2D11's or found one that matched. I haven't posted before and I'm doing this through a lot of trial and error so if you need more info please post and say so and I'll try to provide it.
So rather than have someone try to make sense of the rediculous formula I posted I though I would try to make it simpler by just stating what I need to accomplish and trying to see how to turn that into a VBA function. So I'm kinda looking at a few steps:
Matches first instance of building name in column A with
building name for the row of the output cell.
Is date connected with the output cell >= start date of first entry(which is user entered in column D).
Is date connected with the output cell <= end date of first entry(which is user entered in column E).
Enters Unit name(located in column F) for first instance of the building if Parts 1, 2, and 3 are all True.
If parts 1, 2, or 3 are False then loops to look at next instance of the building name down column 1.
Hopefully this makes things clearer than the formula so I'm able to get help as I'm still pretty stuck due to low knowledge of VBA.
Here is a simple solution...
Building_name = ???
Date = ???
Last_Row = Range("A65536").End(xlUp).Row
For i = 1 To Last_Row
if cells(i,1).value = Building_Name Then
if date >= cells(i,4).value Then
if date <= cells(i,5).value Then
first instance = cells(i,6).value
end if
end if
end if
next
you should add a test at the end to avoid the case where there is no first instance in the table
If I understand correctly, you have a Table T1 made of 3 columns: T1.building, T1.start date, T1.end date.
Then you have 3 parameters: P1=building, P2=start date, P3=end date.
You need to find the first entry in table T1 that "fits" within the input parameters dates, that is:
P1=T1.building
P2<=T1.start date
P3>=T1.end date
If so, you can define a custom function like this
Public Function MyLookup(Key As Variant, DateMin As Variant, DateMax As Variant, LookUpTable As Range, ResultColumn As Integer) As Range
Dim iIndx As Integer
Dim KeyValue As Variant
Dim Found As Boolean
On Error GoTo ErrHandler
Found = False
iIndx = 1
Do While (Not Found) And (iIndx <= LookUpTable.Rows.Count)
KeyValue = LookUpTable.Cells(iIndx, 1)
If (KeyValue = Key) And _
(DateMin <= LookUpTable.Cells(iIndx, 2)) And _
(DateMax >= LookUpTable.Cells(iIndx, 3)) Then
Set MyLookup = LookUpTable.Cells(iIndx, ResultColumn)
Found = True
End If
iIndx = iIndx + 1
Loop
Exit Function
ErrHandler:
MsgBox "Error in MyLookup: " & Err.Description
End Function
That may not be the most performant piece of code in the world, but I think it's explanatory.
You can download this working example

Resources