Date cleanup function - vba

I have this VBA module in my Excel spreadsheet that attempts to clean up the date data, which contains various issues with text being combined with date information. Here is my main load function:
Public lstrow As Long, strDate As Variant, stredate As Variant
Sub importbuild()
lstrow = Worksheets("Data").Range("G" & Rows.Count).End(xlUp).Row
Function DateOnlyLoad(col As String, col2 As String, colcode As String)
Dim i As Long, j As Long, k As Long
j = Worksheets("CI").Range("A" & Rows.Count).End(xlUp).Row + 1
k = Worksheets("Error").Range("A" & Rows.Count).End(xlUp).Row + 1
For i = 2 To lstrow
strDate = spacedate(Worksheets("Data").Range(col & i).Value)
stredate = spacedate(Worksheets("Data").Range(col2 & i).Value)
If (Len(strDate) = 0 And (col2 = "NA" Or Len(stredate) = 0)) Or InStr(1,
UCase(Worksheets("Data").Range(col & i).Value), "EXP") > 0 Then
GoTo EmptyRange
Else
Worksheets("CI").Range("A" & j & ":C" & j).Value =
Worksheets("Data").Range("F" & i & ":H" & i).Value
Worksheets("CI").Range("D" & j).Value = colcode
Worksheets("CI").Range("E" & j).Value = datecleanup(strDate)
'Worksheets("CI").Range("L" & j).Value = dateclean(strDate)
Worksheets("CI").Range("F" & j).Value = strDate
If col2 <> "NA" Then
If IsEmpty(stredate) = False Then
Worksheets("CI").Range("F" & j).Value = datecleanup(stredate)
End If
End If
j = j + 1
End If
EmptyRange:
Next i
End Function
datecleanup function:
Function datecleanup(inputdate As Variant) As Variant
If Len(inputdate) = 0 Then
inputdate = "01/01/1901"
Else
If Len(inputdate) = 4 Then
inputdate = "01/01/" & inputdate
Else
If InStr(1, inputdate, ".") Then
inputdate = Replace(inputdate, ".", "/")
End If
End If
End If
datecleanup = Split(inputdate, Chr(32))(0)
Sample Output:
Column A Column B Column C Column D Column E Column F
125156 Wills, C 11/8/1960 MMR1 MUMPS MUMPS TITER 02/26/2008 POSITIVE
291264 Balti, L 09/10/1981 MMR1 (blank) Measles - 11/10/71 Rubella
943729 Barnes, B 10/10/1965 MMR1 MUMPS MUMPS TITER 10/08/2008 POSITIVE
The Split separates the date from the subsequent text and this works fine, however if there is text that occurs before the date then the output contains the first part of the text. I would like to get only the date (if it exists) from the string and display that, regardless of where it falls in the string. Below are sample results: Column E is the output from the Split logic, Column F is the entire string that is being evaluated from the other worksheet.
Desired Output from above examples: (Column E has correct dates extracted)
Column A Column B Column C Column D Column E Column F
125156 Wills, C 11/8/1960 MMR1 02/26/2008 MUMPS TITER 02/26/2008 POSITIVE
291264 Balti, L 09/10/1981 MMR1 11/10/71 Measles - 11/10/71 Rubella
943729 Barnes, B 10/10/1965 MMR1 10/08/2008 MUMPS TITER 10/08/2008 POSITIVE
What else can I add into my datecleanup function to further refine this? Thanks in advance!

Avoiding a regex, such as in the way suggested in comments is usually a good idea, but in for a penny, in for a pound:
① Use a regex mm/dd/yyyy
(0[1-9]|1[012])[- \/.](0[1-9]|[12][0-9]|3[01])[- \/.](19|20)[0-9]{2}
That pattern comes from ipr101's answer, and proposes a good regex for validating an actual date for mm/dd/yyyy. I have adjusted to correctly escape a couple of characters.
You would need to adjust if can be less digits or different format. Some examples given below.
You could use the function below as:
Worksheets("CI").Range("F" & j).Value = RemoveChars(datecleanup(stredate))
Example test:
Option Explicit
Public Sub test()
Debug.Print RemoveChars("Measles - 11/10/1971 Rubella")
End Sub
Public Function RemoveChars(ByVal inputString As String) As String
Dim regex As Object, tempString As String
Set regex = CreateObject("VBScript.RegExp")
With regex
.Global = True
.MultiLine = True
.IgnoreCase = False
.Pattern = "(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])[- /.](19|20)[0-9]{2}"
End With
If regex.test(inputString) Then
RemoveChars = regex.Execute(inputString)(0)
Else
RemoveChars = inputString
End If
End Function
② For dd/mm/yyyy use:
(0[1-9]|[12][0-9]|3[01])[- \/.](0[1-9]|1[012])[- \/.](19|20)[0-9]{2}
③ And more flexible in case of single day or month (day before month), use:
([1-9]|[12][0-9]|3[01])[- \/.](0?[1-9]|1[012])[- \/.][0-9]{2,4}
You get the idea.
Note:
You can always use something generic like (\d{1,2}\/){2}\d{2,4}, and then validate the function return string with ISDATE(return value).

Related

Split two columns by delimiter and merge together taking a step from each (EXCEL 2016)

Ok so I have two columns of data as follows
Personalisation Max Char | Personaisation Field
1x15x25 | Initial, Name, Date
Previously I was using the following vba function (As excel16 has no TEXTJOIN)
Function TEXTJOIN(delim As String, skipblank As Boolean, arr)
Dim d As Long
Dim c As Long
Dim arr2()
Dim t As Long, y As Long
t = -1
y = -1
If TypeName(arr) = "Range" Then
arr2 = arr.Value
Else
arr2 = arr
End If
On Error Resume Next
t = UBound(arr2, 2)
y = UBound(arr2, 1)
On Error GoTo 0
If t >= 0 And y >= 0 Then
For c = LBound(arr2, 1) To UBound(arr2, 1)
For d = LBound(arr2, 1) To UBound(arr2, 2)
If arr2(c, d) <> "" Or Not skipblank Then
TEXTJOIN = TEXTJOIN & arr2(c, d) & delim
End If
Next d
Next c
Else
For c = LBound(arr2) To UBound(arr2)
If arr2(c) <> "" Or Not skipblank Then
TEXTJOIN = TEXTJOIN & arr2(c) & delim
End If
Next c
End If
TEXTJOIN = Left(TEXTJOIN, Len(TEXTJOIN) - Len(delim))
End Function
This would change 1x15x25 into 1-1, 2-15, 3-25using the following formula
{=TEXTJOIN(", ",TRUE,ROW(INDIRECT("1:" & LEN(A1)-LEN(SUBSTITUTE(A1,"x",""))+1)) & " - " & TRIM(MID(SUBSTITUTE(A1,"x",REPT(" ",999)),(ROW(INDIRECT("1:" & LEN(A1)-LEN(SUBSTITUTE(A1,"x",""))+1)) -1)*999+1,999)))}
Due to the fact, my original method was not specific enough I've been forced to go back to the drawing board.
From the Above, I am wanting to produce the following.
1-2-Initial, 2-15-Name, 3-25-Date
I am a developer but not in visual basic and the worst part Is I know what I would do with a database and PHP just don't have enough knowledge to transfer that to excel.
So I need to either by formula or function
Take 2 Columns and split by a delimiter
Then count the entries on each (Maybe only one)
Then for each in the range create a new string adding the count-col1-col2
I cannot change the data as its given by the supplier
I have a basic understanding of VBA so explain don't belittle
UPDATED (DATA SNAPSHOTS)
This Example uses the formula above a little-jazzed up.
As you can see each row starts the count again Ignore the Personalization/Message line parts I can add these again later
I am in a mega rush so only whipped this up with one row of values (in A1 and B1)
I hope you can step through to understand it, wrap it in another loop to go through your 6000 rows, and change the msgbox to whatever output area you need... 6000 rows should be super quick:
Sub go()
Dim a() As String
Dim b() As String
Dim i As Long
Dim str As String
' split A1 and B1 based on their delimiter, into an array a() and b()
a() = Split(Range("A1").Value2, "x")
b() = Split(Range("B1").Value2, ",")
' quick check to make sure arrays are same size!
If UBound(a) <> UBound(b) Then Exit Sub
' this bit will need amended to fit your needs but I'm using & concatenate to just make a string with the outputs
For i = LBound(a) To UBound(b)
str = str & i + 1 & "-" & a(i) & "-" & b(i) & vbNewLine
Next i
' proof in the pudding
MsgBox str
End Sub
Sub test()
Dim rngDB As Range
Dim vR() As Variant
Dim i As Long
Set rngDB = Range("a2", Range("a" & Rows.Count).End(xlUp)) '<~~personaliation Max Char data range
ReDim vR(1 To rngDB.Count, 1 To 1)
For i = 1 To rngDB.Count
vR(i, 1) = textjoin(rngDB(i), rngDB(i, 2))
Next i
Range("c2").Resize(rngDB.Count) = vR '<~ result wil be recorded in Column C
End Sub
Function textjoin(rng1 As Range, rng2 As Range)
Dim vS1, vS2
Dim vR()
Dim i As Integer
vS1 = Split(rng1, "x")
vS2 = Split(rng2, ",")
ReDim vR(UBound(vS1))
For i = LBound(vS1) To UBound(vS1)
vR(i) = i + 1 & "-" & Trim(vS1(i)) & "-" & Trim(vS2(i))
Next i
textjoin = Join(vR, ",")
End Function
THANK YOU FOR ALL OF THE HELP
I went back to the drawing board having seen the above.
I learnt
That my original use of array formula and TEXTJOIN where over the top and hardly simplistic
That I can use VBA just like any other programming code :)
My Solution simplified from Dy.Lee
Function SPLITANDMERGE(arr1 As String, arr2 As String, Optional del1 As String = "x", Optional del2 As String = ",")
'Arr1 Split'
Dim aS1
'Arr2 Split'
Dim aS2
'Value Array'
Dim r()
'Value Count'
Dim v As Integer
'Split The Values'
aS1 = Split(arr1, del1)
aS2 = Split(arr2, del2)
'Count The Values'
ReDim r(UBound(aS1))
'For All The Values'
For v = LBound(aS1) To UBound(aS2)
'Create The String'
r(v) = "Personalisation_Line " & v + 1 & " - " & Trim(aS1(v)) & " Characters - [" & Trim(aS2(v)) & "]"
Next v
'Join & Return'
SPLITANDMERGE = Join(r, ", ")
End Function
I'm still working on it but I now get the following result.
Will Be Adding:
Value Count Comparison (If we have 4 and 5 Values return "-" to be picked up by conditional formatting)
Conditional plural values (If value 2 in the string is 0 then character instead of characters
If there are any pitfalls or errors anyone can see please do enlighten me. Im here to learn.

VBA date clean up

I have some date data that I want to clean up and remove any text that is in the date.
I have the following code that outputs data to a worksheet, and it has a separate datecleanup function that does some of the date cleanup if there is a missing date, or it is only 4 digits, however I am still getting data outputted that contains a mixture of dates and text (examples below).
Main function:
Function TetanusLoad(col As String, col2 As String)
Dim i As Long, j As Long, k As Long
j = Worksheets("CI").Range("A" & Rows.Count).End(xlUp).Row + 1
k = Worksheets("Error").Range("A" & Rows.Count).End(xlUp).Row + 1
For i = 2 To lstrow
If Len(Worksheets("Data").Range(col & i).Value) = 0 And
Len(Worksheets("Data").Range(col2 & i).Value) = 0 Then
GoTo EmptyRange
Else
strDate = spacedate(Worksheets("Data").Range(col & i).Value)
Worksheets("CI").Range("A" & j & ":C" & j).Value =
Worksheets("Data").Range("F" & i & ":H" & i).Value
Select Case Worksheets("Data").Range(col2 & i).Value
Case "Tdap"
Worksheets("CI").Range("D" & j).Value = "TDA"
Case "Td"
Worksheets("CI").Range("D" & j).Value = "TD"
Case Else
Worksheets("CI").Range("D" & j).Value = "REVIEW"
End Select
Worksheets("CI").Range("E" & j).Value = datecleanup(strDate)
j = j + 1
End If
EmptyRange:
Next i
End Function
datecleanup function:
Function datecleanup(inputdate As Variant) As Variant
If Len(inputdate) = 0 Then
inputdate = "01/01/1901"
Else
If Len(inputdate) = 4 Then
inputdate = "01/01/" & inputdate
Else
If InStr(1, inputdate, ".") Then
inputdate = Replace(inputdate, ".", "/")
End If
End If
End If
datecleanup = inputdate
End Function
Sample data output examples for column E that I am trying to correct:
07/06/1993 - HAD ALLERGIC REACTION ; ARM SWELLED AND GOT RED AND HOT
09/23/2004 - REPORTS REACTION TO TETANUS SHOT
12/03/2015 Rubelo reported
I don't want the additional text included, as this should be a date only field. How can I accomplish this? Ideally I would like it to be referenced in the datecleanup function as other functions use this as well.
Taking Nathan's and expanding on it in case of text before date:
Function dateclean(strInput As String) As String
Dim strSplits As Variant, i As Integer, dateFound As String
strSplits = Split(strInput, Chr(32))
For i = 0 To UBound(strSplits)
If strSplits(i) Like "*/*/*" Then
dateFound = strSplits(i)
Exit For
End If
Next i
dateclean = dateFound
End Function
Something like this
function dateclean(strInput as string) as string
dateclean=split(strInput,chr(32))(0)
end function
Not sure what all your code is meant to be doing - it doesn't say where lstRow is defined.
This sample has your examples in the range Data!D2:D4.
The output will appear in the range CI!D2:D4.
Note - I've updated some variable names (although they're not used).
E.g. It's a bit more obvious what CI_LastRow contains, rather than figuring out what j stands for.
Sub Test()
TetanusLoad 4, 5
End Sub
Public Sub TetanusLoad(col As Long, col2 As Long)
Dim CI_LastRow As Long, Error_LastRow As Long
Dim Data_Range As Range, rCell As Range
CI_LastRow = Worksheets("CI").Cells(Rows.Count, 1).End(xlUp).Row + 1
Error_LastRow = Worksheets("Error").Cells(Rows.Count, 1).End(xlUp).Row + 1
'This is the range containing your date/text strings.
With Worksheets("Data")
Set Data_Range = .Range(.Cells(1, col), .Cells(.Rows.Count, col).End(xlUp))
End With
For Each rCell In Data_Range
Worksheets("CI").Cells(rCell.Row, 5) = datecleanup(rCell)
Next rCell
End Sub
Function datecleanup(inputdate As Variant) As Variant
Dim re, match
Set re = CreateObject("vbscript.regexp")
re.Pattern = "[\d]+[\/-][\d]+[\/-][\d]+"
re.Global = True
For Each match In re.Execute(inputdate)
If IsDate(match.Value) Then
datecleanup = CDate(match.Value)
Exit For
End If
Next
Set re = Nothing
End Function
The datecleanup function is a copy of the FormatOutput function found on this link:
VBA Regular Expression to Match Date

Converting To String Removes 0's after Decimal

I am copying data from a source workbook to a destination workbook by using the Implode() method below. The issue that I have is that in the source workbook the format will be 7.00 but in the destination workbook the format will be 7, I believe this is due to CStr(MyR(1, i)) i.e. ConvertToString. How can I alter this method so that if the column is in a numeric format that once it is copied to the destination workbook, it is once again in a numeric format?
Private Function Implode(ByVal R As Range, Optional ByVal D As String = strSeparator) As String
Dim i As Long, ii As Long, str As String, MyR() As Variant
MyR = R
For i = 1 To R.Columns.Count
isPercent = False
If iPC > 0 And IsNumeric(MyR(1, i)) And MyR(1, i) <> "" Then
For ii = 1 To iPC
If i = PercCols(ii) Then
isPercent = True
Exit For
End If
Next ii
End If
str = CStr(MyR(1, i))
If InStr(1, str, D) > 0 Then str = """" & str & """"
If i = 1 Then
Implode = str
Else
Implode = Implode & D & str
End If
Next i
End Function
Can try
If IsNumeric(MyR(1, i)) Then 'Check for numeric
Round(CDec(MyR(1, i)),2)
Else
CStr(MyR(1, i))
End If
CDec allows those without decimal to be displayed as whole numbers
More Info on Conversion Function

Macro to compare and highlight case-sensitive data

I came across a macro that compares data pasted in column B with column A and highlights column B if not an Exact match with column A.
Sub HighlightNoMatch()
Dim r As Long
Dim m As Long
m = Range("B" & Rows.Count).End(xlUp).Row
Range("B1:B" & m).Interior.ColorIndex = xlColorIndexNone
For r = 1 To m
If Evaluate("ISERROR(MATCH(TRUE,EXACT(B" & r & ",$A$1:$A$30),0))") Then
Range("B" & r).Interior.Color = vbRed
End If
Next r
End Sub
How do I change the code to achieve as below -
I want the code to highlight Column F on sheet2, if it is not an exact match with data in Column B on sheet1."
Rather than having a fixed range ($A$1:$A$30) I would loop through each value in the range and check for a match:
Sub HighlightNoMatch()
Dim t As Long
Dim m As Long
m = Worksheets("Sheet2").Range("F" & Rows.Count).End(xlUp).Row
t = Worksheets("Sheet1").Range("B" & Rows.Count).End(xlUp).Row
Worksheets("Sheet2").Range("F1:F" & m).Interior.ColorIndex = xlColorIndexNone
For x1 = 1 To m
For x2 = 1 To t
If Worksheets("Sheet2").Range("F" & x1).Value = Worksheets("Sheet1").Range("B" & x2).Value Then
Exit For
ElseIf Worksheets("Sheet2").Range("F" & x1).Value <> Worksheets("Sheet1").Range("B" & x2).Value And x2 = t Then
Worksheets("Sheet2").Range("F" & x1).Interior.Color = vbRed
End If
Next x2
Next x1
End Sub

check number falls between range

column A and Column C is the range and column B is the reference value which I have to compare with Column A and Column C .
Eg: (B>A) and (B
Basically I want to check whether column B falls between Column A and column C
Here is the code which I have prepared but this is not working and this is for single cell:
Sub a()
Dim x As Integer
Dim y As Integer
x = Worksheets("Sheet1").Range("A1").Value
y = Worksheets("Sheet1").Range("B1").Value
Z = Worksheets("Sheet1").Range("C1").Value
If Z > x Then
Worksheets("Sheet1").Range("D1") = "Correct"
End If
End Sub
you can do this way:
Sub main()
With Worksheets("Sheet1")
With .Range("D1:D" & .Cells(.Rows.Count, 1).End(xlUp).row)
.FormulaR1C1 = "=IF(AND(RC2>=RC1,RC2<=RC3),""Correct"",""Wrong"")"
.Value = .Value
End With
End With
End Sub
You can use a simple formula for this:
=IF(AND(B1>=A1,B1<=C1),"Correct","Wrong")
If you still need vba then use this:
Sub RANGEFALL()
Dim wk As Worksheet, frow As Long, i As Long
Set wk = Sheet1
frow = wk.Range("A" & Rows.Count).End(xlUp).Row
For i = 1 To frow
If wk.Range("B" & i).Value >= wk.Range("A" & i).Value And wk.Range("B" & i).Value <= wk.Range("C" & i).Value Then
wk.Range("D" & i).Value = "Correct"
Else
wk.Range("D" & i).Value = "Wrong"
End If
Next i
End Sub