VBA - Find date within text containing format 'DDMMYYYY' - excel

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

Related

VBA Finding Numbers, Letters, and Characters in Cell and Replacing Content of Cell With Only Numbers/Letters

Having another problem.
I am creating another Excel tool at my facility and currently what I have pulls data from a purchase order tracking website and filters out superfluous data, leaving only the purchase order label itself, a single quotation mark, and an end bracket. I need to remove the quotation mark and ending bracket so I only have the PO itself, as I need this to inject into another site's URL. I have tried using wildcards with some code I wrote however the PO will get replaced with several asterisks, aka "wildcards," instead. I am probably overlooking something obvious, but I can't figure it out.
Example of data:
Code example:
Sub Filter_DockMaster_Data2()
Dim Main As Worksheet
Set Main = Worksheets("Main")
Dim ISA_List As Worksheet
Set ISA_List = Worksheets("ISA_List")
Dim ISA_Results As Worksheet
Set ISA_Results = Worksheets("ISA_Results")
Dim ISA_Raw As Worksheet
Set ISA_Raw = Worksheets("ISA_Raw")
Worksheets("ISA_Results").Select
Range("A1").Select
Do Until IsEmpty(ActiveCell)
ActiveCell.replace What:="********"" ]", Replacement:="********", LookAt:= _
xlPart, SearchOrder:=xlByRows, MatchCase:=False, SearchFormat:=False, _
ReplaceFormat:=False
ActiveCell.Offset(1, 0).Select
Loop
End Sub
Hopefully this makes sense.
Quick notes, the length of the PO will vary as time goes on, so I would like to make it dynamic if possible. Any help is appreciated, thanks.
Encode for a URL
How about the function intended for making text URL-friendly? :)
=ENCODEURL(A1)
For example, this:
1234ABCD']
...becomes encoded as:
1234ABCD%27%5D
...ready to insert in a query parameter (URL) string. See the documentation including further examples at at the link below.
Text Functions like LEFT
That said, there are several other ways to do this.
You said "replace" but it looks like you just need to cut off the last 2 characters?
This cuts the last 2 characters of the text in A1:
=LEFT(A1,LEN(A1)-2)
SUBSTITUTE Function
If you do want to "replace" text in a cell, you can use SUBSTITUTE:
Example:
If cell A1 contains:
1234ABCD']
...you could enter in another cell:
=SUBSTITUTE(A1,"]","")
...which would remove only the ]. You can also nest functions. To remove both the ] and the ', use this formula instead:
=SUBSTITUTE(SUBSTITUTE(A1,"]",""),"'","")
The SUBSTITUTE function syntax:
SUBSTITUTE( text, old_text, new_text, [instance_num] )
The SUBSTITUTE function syntax has the following arguments:
Text - (Required) The text or the reference to a cell containing text for which you want to substitute characters.
Old_text - (Required) The text you want to replace.
New_text - (Required) The text you want to replace old_text with.
Instance_num - (Optional) Specifies which occurrence of old_text you want to replace with new_text. If you specify instance_num, only that instance of old_text is replaced. Otherwise, every occurrence of old_text in text is changed to new_text.
(Source & More Info)
More Information:
Microsoft Support: ENCODEURL function (Excel)
Stack Overflow : Use MID, LEN, and FIND functions to extract certain cell portions?
Microsoft Support : SUBSTITUTE Function
JayTray : The LEFT, RIGHT and MID formulas in Excel
Microsoft Support : Text functions (reference)
Your problem shouldn't be about repairing the the data you've extracted from some source. It really should be about fixing the retrieval procedure so you do not get rogue characters.
With that said, this sub procedure should quickly remove any rogue characters from column A.
Option Explicit
Sub posOnly()
Dim i As Long, str As String, rgx As Object
Set rgx = CreateObject("VBScript.RegExp")
'pattern for A-Z (case sensitive) or 0-9 exactly 8 characters/digits in length
rgx.Pattern = "[A-Z0-9]{8}"
With Worksheets("ISA_Results")
For i = 2 To .Cells(.Rows.Count, "A").End(xlUp).Row
str = .Cells(i, "A").Value2
If rgx.Test(str) Then
str = rgx.Execute(str).Item(0)
.Cells(i, "A") = str
End If
Next i
End With
End Sub

Prevent Partial Duplicates in Excel

I have a worksheet with products where the people in my office can add new positions. The problem we're running into is that the products have specifications but not everybody puts them in (or inputs them wrong).
Example:
"cool product 14C"
Is there a way to convert Data Valuation option so that it warns me now in case I put "very cool product 14B" or anything that contains an already existing string of characters (say, longer than 4), like "cool produKt 14C" but also "good product 15" and so on?
I know that I can prevent 100% matches using COUNTIF and spot words that start/end in the same way using LEFT/RIGHT but I need to spot partial matches within the entries as well.
Thanks a lot!
If you want to cover typo's, word wraps, figure permutations etc. maybe a SOUNDEX algorithm would suit to your problem. Here's an implementation for Excel ...
So if you insert this as a user defined function, and create a column =SOUNDEX(A1) for each product row, upon entry of a new product name you can filter for all product rows with same SOUNDEX value. You can further automate this by letting user enter the new name into a dialog form first, do the validation, present them a Combo Box dropdown with possible duplicates, etc. etc. etc.
edit:
small function to find parts of strings terminated by blanks in a range (in answer to your comment)
Function FindSplit(Arg As Range, LookRange As Range) As String
Dim LookFor() As String, LookCell As Range
Dim Idx As Long
LookFor = Split(Arg)
FindSplit = ""
For Idx = 0 To UBound(LookFor)
For Each LookCell In LookRange.Cells
If InStr(1, LookCell, LookFor(Idx)) <> 0 Then
If FindSplit <> "" Then FindSplit = FindSplit & ", "
FindSplit = FindSplit & LookFor(Idx) & ":" & LookCell.Row
End If
Next LookCell
Next Idx
If FindSplit = "" Then FindSplit = "Cool entry!"
End Function
This is a bit crude ... but what it does is the following
split a single cell argument in pieces and put it into an array --> split()
process each piece --> For Idx = ...
search another range for strings that contain the piece --> For Each ...
add piece and row number of cell where it was found into a result string
You can enter/copy this as a formula next to each cell input and know immediately if you've done a cool input or not.
Value of cell D8 is [asd:3, wer:4]
Note the use of absolute addressing in the start of lookup range; this way you can copy the formula well down.
edit 17-Mar-2015
further to comment Joanna 17-Mar-2015, if the search argument is part of the range you're scanning, e.g. =FINDSPLIT(C5; C1:C12) you want to make sure that the If Instr(...) doesn't hit if LookCell and LookFor(Idx) are really the same cell as this would create a false positive. So you would rewrite the statement to
...
...
If InStr(1, LookCell, LookFor(Idx)) <> 0 And _
Not (LookCell.Row = Arg.Row And LookCell.Column = Arg.Column) _
Then
hint
Do not use a complete column (e.g. $C:$C) as the second argument as the function tends to become very slow without further precautions

Excel Substrings

I have two unordered sets of data here:
blah blah:2020:50::7.1:45
movie blah:blahbah, The:1914:54:
I want to extract all the data to the left of the year (aka, 1915 and 1914).
What excel formula would I use for this?
I tried this formula
=IF(ISNUMBER(SEARCH(":",A1)),MID(A1,SEARCH(":",A1),300),A1)
these were the results below:
: blahblah, The:1914:54::7
:1915:50::7.1:45:
This is because there is a colon in the movie title.
The results I need consistently are:
:1914:54::7.9:17::
:1915:50::7.1:45::
Can someone help with this?
You can use Regular Expressions, make sure you include a reference for it in your VBA editor. The following UDF will do the job.
Function ExtractNumber(cell As Range) As String
ExtractNumber = ""
Dim rex As New RegExp
rex.Pattern = "(:\d{4}:\d{2}::\d\.\d:\d{2}::\d:\d:\d:\d:\d:\d:\d)"
rex.Global = True
Dim mtch As Object, sbmtch As Object
For Each mtch In rex.Execute(cell.Value)
ExtractNumber = ExtractNumber & mtch.SubMatches(0)
Next mtch
End Function
Without VBA:
In reality you don't want to find the : You want to find either :1 or :2 since the year will either start with 1 or 2This formula should do it:
=MID(A1,MIN(IFERROR(FIND(":1",A1,1),9999),IFERROR(FIND(":2",A1),9999)),9999)
Look for a four digit string, in a certain range, bounded by colons.
For example:
=MID(A1,MIN(FIND(":" &ROW(INDIRECT("1900:2100"))&":",A1 &":" &ROW(INDIRECT("1900:2100"))&":")),99)
entered as an array formula by holding down ctrl-shift while hitting Enter would ensure years in the range 1900 to 2100. Change those values as appropriate for your data. The 99 at the end represents the longest possible string. Again, that can be increased as required.
You can use the same approach to return just the left hand part, up to the colon preceding the year:
=LEFT(A1,-1+MIN(FIND(":" &ROW(INDIRECT("1900:2100"))&":",A1 &":" &ROW(INDIRECT("1900:2100"))&":")))
Here is a screen shot, showing the original data in B1:B2, with the results of the first part in B4:B5, and the formula for B4 showing in the formula bar.
The results for the 2nd part are in B7:B9

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

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

Removing tags from formatted text in Excel cells

Walk with me for a moment.
I have built an Access application to manage data for an internal project at my company. One of the functions of this application is queries the database, then outputs the queries to an Excel spreadsheet, then formats the spreadsheet to spec.
One of the cells of the output is a large amount of text from a Rich Text Memo field in the database. When the rich text is sent to Excel it carries with it HTML tags indicating bold or italic, so for the output I have to add the formatting and remove the tags.
Here is an example of the text I need to format (this text is in a single cell):
For each participant, record 1 effort per lesson delivered
• Time Spent = # minutes spent on lesson
<strong>OR</strong>
For each participant, record 1 effort per month
• Time Spent = total # minutes spent on lessons that month
<strong>Note:</strong> Recording 1 effort per lesson is recommended but not required
<strong>Note:</strong> Use groups function in ABC when appropriate (see <u>Working With Groups</u> in ABC document library on the ABC portal)
I have a three neat little recursive functions for formatting the text, here is the bolding function:
Function BoldCharacters(rng As Range, Optional ByVal chrStart As Long)
'This will find all the "<strong></strong>" tags and bold the text in between.
Dim tagL As Integer
tagL = 8
rng.Select
If chrStart = 0 Then chrStart = 1
b1 = InStr(chrStart, ActiveCell.Value, "<strong>") + tagL
If b1 = tagL Then Exit Function
b2 = InStr(b1, ActiveCell.Value, "</strong>")
ActiveCell.Characters(Start:=b1, Length:=b2 - b1).Font.Bold = True
'Remove the tags
'ActiveCell.Characters(Start:=1, Length:=1).Delete
'ActiveCell.Characters(Start:=b2 - tagL, Length:=tagL + 1).Delete
'Recursion to get all the bolding done in the cell
Call BoldCharacters(ActiveCell, b2 + tagL + 1)
End Function
Now here's the issue. This formats the text nicely. But the "ActiveCell.Characters.Delete" method fails when I attempt to use it to remove the tags because the cell contains more than 255 characters. So I can't use the delete method.
And when I do this:
With xlApp.Selection
.Replace what:="<strong>", replacement:=""
The tags are all removed, but the formatting is all destroyed! So what's the point!?
I'm looking for a way of formatting my text and removing the tags. I'm considering taking the large bit of text and 'chunking' it up into a number of cells, processing the formatting and re-assembling, but that sounds difficult, prone to error, and might not even work.
Any ideas!?
Thanks!
You might want to remove the formatting before exporting the data to Excel. At the same time that you remove the formatting, store the formatting information (location, length, style) to a data structure. After you export the "plain text" data you could then iterate over your structure and apply the formatting in Excel. This could be a time consuming process depending upon how many records you plan on exporting at a given time, but it would remove the limitation imposed by Excel.
If it's well formed html (ie it always has closing tags) then you could use a regular expression.
Dim data As String
data = "For each participant, record 1 effort per lesson delivered • Time Spent = # minutes spent on lesson <strong>OR</strong> For each participant, record 1 effort per month • Time Spent = total # minutes spent on lessons that month <strong>Note:</strong> Recording 1 effort per lesson is recommended but not required <strong>Note:</strong> Use groups function in ABC when appropriate (see <u>Working With Groups</u> in ABC document library on the ABC portal)"
Dim r As New RegExp
r.Pattern = "<(.|\n)*?>"
r.Global = True
Debug.Print r.Replace(data, "")
To use the RegExp object, set a reference to Microsoft VBScript Regular Expressions 5.5.
hth
Ben
Something along these lines might be useful:
Sub DoFormat(rng As Range)
Dim DataObj As New MSForms.DataObject
Dim s As String, c As Range
For Each c In rng.Cells
s = "<html>" & Replace(c.Value, " ", " ") & "</html>"
DataObj.SetText s
DataObj.PutInClipboard
c.Parent.Paste Destination:=c
Next c
End Sub
You'll need a reference to "Microsoft Forms 2.0 Object Library"

Resources