How can I pinpoint a character in a string and replace it - excel

I am attempting to write a Marco to select the highlighted cell in the image attached (B11), locate the highlighted colon (20th character from the left) and replace it with a dot and loop this to do all 2500 rows in "B"
I am new to writing macros an would appreciate any help you can give me

Small showcase of how would you search for the last occurrence of ":" in Range("B11") and replace it with a "."
Sub replaceTest()
Dim val As String
Dim pos As Long
Dim rng As Range
Set rng = Range("B11")
val = rng.Value
pos = InStrRev(val, ":")
If pos > 0 Then
Mid$(val, pos, 1) = "."
rng.Value = val
End If
Set rng = Nothing
End Sub

Since your date times 18/10/2022 11:42:10:358 look to be text. I recommend to convert them into a numeric date time (so you can calculate with it and use comparisons which is greater or smaller).
Therefore you need to split the text up into its parts and turn it into a real numeric date using DateSerial and TimeSerial. Finally you need to calculate the milliseconds and add them.
Then you can use .NumberFormat = "DD\/MM\/YYYY hh:mm:ss.000" to format it as you like.
Public Function StringToDateTime(ByVal InputString As String) As Double
Dim InputDateTime() As String ' Split into DD/MM/YYYY and hh:mm:ss:000
InputDateTime = Split(InputString, " ")
Dim InputDate() As String ' Split into DD and MM and YYYY
InputDate = Split(InputDateTime(0), "/")
Dim InputTime() As String ' Split into hh and mm and ss and 000
InputTime = Split(InputDateTime(1), ":")
Dim RetVal As Double
RetVal = DateSerial(InputDate(2), InputDate(1), InputDate(0)) + TimeSerial(InputTime(0), InputTime(1), InputTime(2)) + InputTime(3) / 24 / 60 / 60 / 1000
StringToDateTime = RetVal
End Function
Public Sub Example()
Dim Cell As Range
For Each Cell In Range("A1:A5")
Cell.Value2 = StringToDateTime(Cell.Value2)
Cell.NumberFormat = "DD\/MM\/YYYY hh:mm:ss.000"
Next Cell
End Sub

Related

Split Column to Date and Time

I am trying to split a column that contains a combination of Date and Time into two columns, where date and time are separated.
Column C contains a combination of date and time, for example "2022-01-01 09:30:00".
This should be split into Date in Column D and Time in Column E, in the format "dd.mm.yyyy" and "hh:mm":
Column D with 01.01.2022
Column E with "09:30"
I need to compare with a different sheet, where they are in this format.
Although I managed to split Date and Time into two columns the Time format is wrong.
I found suggestions to use Int() to get the date, and then subtract to get the time, however my date seems to be string. I tried to format my column to a Date datatype by using the Cdate function, however this resulted in an error.
As I don't necessarily need the value to have this datatype, I thought I could work with the Left() and Right() function. This first gave a problem but by including a string in between, I am getting closer to what I want.
Dim iAircol As Integer
Dim lastrow As Integer
Dim i As Integer
Dim str1 As String
Dim str2 As String
Dim spacepos as Int
iAircol= Worksheets(ws).Cells.Find(What:="Airdate", SearchDirection:=xlPrevious, SearchOrder:=xlByColumns).Column
lastrow = Range("A" & Rows.Count).End(xlUp).Row
For i = 2 To lastrow
spacepos = InStr(Cells(i, iAircol), " ")
str1 = Left(Cells(i, iAircol).Value, spacepos)
Cells(i, iAircol + 1) = str1
str2 = Left(Right(Cells(i, iAircol).Value, Len(Cells(i, iAircol)) - spacepos), 6)
Cells(i, iAircol + 2) = str2
Next i
Time value still is in "hh:mm:ss":
I give the cell the first 5 characters of the total time, so no idea why it ends up with all 8 characters again, and this should be a string now, but Debug.Print gives me the Type "Date" for the date, and a Double for the Time.
Use DateValue and TimeValue, they are exactly for this:
Cells(i, iAircol + 1) = DateValue(Cells(i, iAircol))
Cells(i, iAircol + 2) = TimeValue(Cells(i, iAircol))
Then apply the Format you prefer to the two date and time columns, as these will hold true DateTime values, not text.
Please, use the next function to split the string as you need:
Function splitDateTime(strTime As String) As Variant
Dim d As Date, t As Date, arrD
arrD = Split(Split(strTime, " ")(0), "-")
d = DateSerial(CLng(arrD(0)), CLng(arrD(1)), CLng(arrD(2)))
t = CDbl(CDate(Format(Split(strTime, " ")(1), "hh:mm")))
splitDateTime = Array(d, t)
End Function
It can be tested like this:
Sub testSplitDateTime()
Dim arr, ac As Range
Set ac = ActiveCell 'in the active cell should be the string to be split/converted...
arr = splitDateTime(ac.value)
ac.Offset(0, 2).EntireColumn.NumberFormat = "HH:mm"
Range(ac.Offset(0, 1), ac.Offset(0, 2)).value = arr
End Sub

How to turn general data written as fractions into 3 place decimal numbers. Replace " 0." with "."

I'm trying to turn general data written as fractions like 3/4" or 13 7/32" into 3 place decimal numbers such as 0.750 or 13.219.
I have a working table replacement that handles 0 to 1" fractions. It can't handle the mixed numbers like 13 7/32". It leaves me with 13 0.219 which is why I need to replace " 0." with "." to join the 13 and 219 together with a decimal.
We do this data conversion in multiple steps and hand type because Excel tries converting some fractions like 3/4" into a date.
Original data
Resulting data
Sub FractionConvertMTO()
'this section works
For i = 6 To 70
Worksheets("BOM").Range("F6:H48").Select
Selection.Replace what:=Cells(i, 21).Value, Replacement:=Cells(i, 22).Value, _
LookAt:=xlPart, SearchOrder:=xlByRows, MatchCase:=False
Next
'this section doesn't work
For i = 6 To 70
Worksheets("BOM").Range("F6:H48").Select
str1 = " "
str1 = Trim(Replace(str1, " ", "+"))
Next
'this section changes the format.
For i = 66 To 130
Range("F6:H48").NumberFormat = "0.000"
Next
'this section is supposed to add an = sign in front of the cell contents but doesn't work.
Dim Cell As Range
For Each Cell In Range("F6:H48")
Cell.Value = "=" & Cell.Value
Next Cell
'this section works to highlight the first cell
Worksheets("BOM").Cells(1, 1).Select
End Sub
I dug up the following method from my library of useful functions. It converts numbers represented as a fractional string to the numeric equivalent. Simply loop through the cells needing conversion and call this method:
Public Function FractionToNumber(ByVal Value As String, Optional ByVal Digits As Long = 0) As Double
Dim P As Integer
Dim N As Double
Dim Num As Double
Dim Den As Double
Value = Trim$(Value)
P = InStr(Value, "/")
If P = 0 Then
N = Val(Value)
Else
Den = Val(Mid$(Value, P + 1))
Value = Trim$(Left$(Value, P - 1))
P = InStr(Value, " ")
If P = 0 Then
Num = Val(Value)
Else
Num = Val(Mid$(Value, P + 1))
N = Val(Left$(Value, P - 1))
End If
End If
If Den <> 0 Then N = N + Num / Den
FractionToNumber = Round(N, Digits)
End Function
You may also code something like the following:
Sub FractionConvertMTO()
Dim rng As Range
Dim Arr As Variant
Arr = Worksheets("MTO").Range("F6:H48")
For Row = 1 To UBound(Arr, 1)
For col = 1 To UBound(Arr, 2)
str1 = Arr(Row, col)
pos1 = InStr(str1, " ")
pos2 = InStr(str1, "/")
If pos2 = 0 Then
N = val(str1)
Num = 0: Den = 1
Else
If pos1 And pos1 < pos2 Then
N = val(Left$(str1, pos1 - 1))
Num = val(Mid$(str1, pos1 + 1))
Else
N = 0
Num = val(Left$(str1, pos2 - 1))
End If
Den = val(Mid$(str1, pos2 + 1))
End If
Arr(Row, col) = N + Num / Den
Next col
Next Row
Worksheets("MTO").Range("F6", "H48") = Arr
End Sub
If you dispose of the newer dynamic array features (vers. 2019+,MS365) you might write the results in one go to the entire original range (target range) as follows (overwriting the existing range; otherwise define a given offset to identify another target range: rng.Offset(,n)=..).
Tip: make a backup copy before testing (as it overwrites rng)!
Note that this example assumes the " character (asc value of 34).
A) First try via tabular VALUE() formula evaluation
Caveat: converting blanks by VALUE() would be written as #VALUE! results, which would need a further loop. To avoid this you can prefix a zero to the formulae myFormula = "=VALUE(SUBSTITUTE(" & """0""&" & rng.Address & ","""""""",""""))" so that results would be displayed as zero.
Sub ChangeToFractionValues()
'1) define original range to be replaced
Dim rng As Range
Set rng = ThisWorkbook.Worksheets("BOM").Range("F6:H48")
'2) define tabular formula
Dim myFormula As String
'myFormula = "=VALUE(SUBSTITUTE(" & rng.Address & ","""""""",""""))"
'Alternative to avoid #VALUE! displays for blanks:
myFormula = "=VALUE(SUBSTITUTE(" & """0""&" & rng.Address & ","""""""",""""))"
'Debug.Print myFormula
'3) overwrite original range (otherwise code an offset rng.Offset(,n).Value = ...
rng.Value2 = rng.Parent.Evaluate(myFormula)
End Sub
Conclusion due to comment:
Though fast, this approach has a big disadvantage: Excel interpretes date-like numbers as such, transforms them internally to dates by returning the numeric part here, so a cell input of 3/4" would return the corresponding date value of the current year for March 4th.
B) Reworked code based on direct cell evaluations in a loop //Edit
Similar to the above processing this approach is also based on evaluation, but collects all formulae as strings in a variant datafield array v, which allows to manipulate and evaluate each cell input individually:
Sub ChangeToFractionValues()
'1) define original range to be replaced
Dim rng As Range
Set rng = ThisWorkbook.Worksheets("BOM").Range("F6:H48")
'2) assign formula strings to variant 1-based 2-dim data field array
Dim v As Variant
v = rng.Formula2
'3) evaluate results in a loop
Dim i As Long, j As Long
For i = 1 To UBound(v)
For j = 1 To UBound(v, 2)
v(i, j) = Evaluate("0" & Replace(v(i, j), Chr(34), ""))
Next j
Next i
'4) overwrite original range (otherwise code an offset rng.Offset(,n).Value = ...
rng.Value = v
End Sub
str1 = trim(Replace(str1, "0.", "."))

How to always have 2 decimal places on a Double

I have a macro that goes through a list of text, extracts the dollar amounts, increase them by 12%, and replaces the text with the updated dollar amounts.
This what a couple rows of data looks like:
This is the result after I run the macro:
I would need the 72.8 to be 72.80 tho, for example.
Sometimes the result would just have 1 decimal place and sometimes it would have 3. The Round function works fine for me with truncating the result down to 2 decimal places, but doesn't help adding a 0 to keep the number at two decimal places.
I need a way to have fill the second decimal place with a 0 if the result only has 1 decimal place.
This is the macro:
Function onlyDigits(s As String) As String
' Variables needed (remember to use "option explicit"). '
Dim retval As String ' This is the return string. '
Dim i As Integer ' Counter for character position. '
' Initialise return string to empty '
retval = ""
' For every character in input string, copy digits to '
' return string. '
For i = 1 To Len(s)
If Mid(s, i, 1) >= "0" And Mid(s, i, 1) <= "9" Then
retval = retval + Mid(s, i, 1)
End If
Next
' Then return the return string. '
onlyDigits = retval
End Function
Sub ChangeDollarAmount()
Dim qtyspec As String
Dim previousDollarIndex As Integer
Dim dollarSignCount As Integer
Dim dollarString As String
Dim originalDollarAmount As String
Dim changedDollarAmount As Double
Dim isANumber As Boolean
previousDollarIndex = 1
' row count
lastrow = ActiveSheet.Range("A1").CurrentRegion.Rows.Count
For Each cell In Range("K2:K" & lastrow)
Debug.Print cell.Formula
previousDollarIndex = 1
qtyspec = cell.Formula
dollarSignCount = (Len(cell.Formula) - Len(Replace(cell.Formula, "$", ""))) / Len("$")
' loop through dollar amounts in text
For i = 1 To dollarSignCount
isANumber = False
previousDollarIndex = InStr(previousDollarIndex + 1, cell.Formula, "$")
originalDollarAmount = Mid(cell.Formula, previousDollarIndex, 8)
Do While isANumber = False
If Not IsNumeric(Right(originalDollarAmount, 1)) Then
originalDollarAmount = Left(originalDollarAmount, Len(originalDollarAmount) - 1)
Else
isANumber = True
End If
Loop
' extract only digits from dollar amount ($345.23 -> 34523)
dollarAmount = onlyDigits(originalDollarAmount)
' add decimal point and increase dollar amount by 12% (34523 -> 345.23 -> 386.66)
changedDollarAmount = Round(CDbl(dollarAmount) * 1.12 * 0.01, 2)
' update the dollar amount in the text
cell.Formula = Replace(cell.Formula, originalDollarAmount, "$" + CStr(changedDollarAmount))
Next i
Next cell
End Sub
changedDollarAmount = CDbl(dollarAmount) * 1.12 * 0.01
cell.Formula = Replace(cell.Formula, originalDollarAmount, Format$(changedDollarAmount, "$0.00"))

How to highlight and list missing values in a letter-number sequence

I'm using Excel 2010. I have a column of data that contains four-digit sequences. These sequences consist of and iterate through a number of the alphabet followed by a number from 100-999. So A100, A101, A102... A999, then B100, B101... all the way up until Z999. There are 24,000 of these sequences (i.e. rows).
I first tried the following VBA script:
Function MissingNumbers(Rng As Range) As String
Dim X As Long, MaxNum As Long
MaxNum = WorksheetFunction.Max(Rng)
ReDim Nums(1 To MaxNum)
For X = 1 To MaxNum
If Rng.Find(X, LookAt:=xlWhole) Is Nothing Then
MissingNumbers = MissingNumbers & ", " & X
End If
Next
MissingNumbers = Mid(MissingNumbers, 3)
End Function
...but I ran into an error when calling it with =MissingNumbers(A1:A23400), which I'm guessing is because the function can't parse the alphabetic letters at the beginning of the sequences.
My question: what formula can I use to both highlight and separately list missing values in my first column?
For extra clarification, missing values in the range
C996
C998
C999
D101
...would be...
C996
*C997*
C998
C999
*D100*
D101
Thanks in advance.
There's perhaps more speedy way to do this ... but this is tested and produced results on a small sample.
Function MissingSequence(Rng As Range) As String
Dim iCnt As Integer
For iCnt = 65 To 90 'ASCI characters for alphabet A-Z
Dim iNum As Integer
For iNum = 100 To 999
Dim sCheck As String
sCheck = Chr(iCnt) & iNum
If Rng.Find(sCheck, lookat:=xlWhole) Is Nothing Then
Dim sMissingNumbers As String
sMissingNumbers = sMissingNumbers & "," & sCheck
End If
Next
Next
MissingSequence = Mid(sMissingNumbers, 2)
End Function

Parsing and comparing a complicated string

I am hoping someone could help me out with a VBA Excel macro.
I have received a worksheet in Excel 2007 which contains product names in one column, and I need to sort this into a logical format so I can use it. However, the list itself is not in any kind of logical order, is 10 000 rows long and I am going to have to do this every month!!
Basically, what I would like to do is search for certain keywords which are common to most of the entries and move them into separate cells in different columns (but in the same row as the original entry).
Regarding keywords: There are 3 different types, two of which I have a complete list of.
Example of keywords: some are measures such as cm (centimetre), mm (millimetre), m (metre) etc.). Then there are other keywords such as % and finally a last set of keywords which is wood, plastic, glass etc.
If this was not complicated enough, the measures (cm for example) are duplicated in some instances and are important details so I cant just separate them but would ideally like them in two adjacent cells.
Fortunately, there is a space after each measure, % sign and item material.
Working from right to left is the easiest way I can think of achieving this as the first description in the string varies wildly between entries and that can stay as is.
So, below is an example string, lets say this is in Cell A1. (Inverted commas are not included in the string and the word "by" appears in only about 100 cases. Usually it is missing...)
"Chair Leg Wood 100% 1m by 20cm"
I would ideally like for the string to be split up into cells as follows
Cell B1 - Chair Leg
Cell C1 - Wood
Cell D1 - 1m
Cell E1 - 2cm
Cell F1 - 100%
Having the % measures in the same column would be extremely helpful
Can anyone please help me with this or the beginnings of a macro which does this and then moves down the list - I have tried using some basic "find" and "len" formulas but really am at my wits end on how to deal with this!
The task boils down to defining a robust definition of the structure of the input data.
Form the info provided a candidate definition might be
<Description, one or more words> <Material, one word> <A value followd by %> <Dimension A> <optional "by"> <Dimension B>
The following macro will process data that conforms this this spec. The definition may need
expanding, eg two word materials (eg Mild Steel)
You will need to add error handling in case any rows don't conform, eg no % in the string, or % character elsewhere in string
Option Explicit
Dim dat As Variant
Sub ProcessData()
Dim r As Range
Dim i As Long
Set r = Intersect(ActiveSheet.UsedRange, ActiveSheet.Columns(1)).Resize(, 5)
dat = r
For i = 1 To UBound(dat, 1)
ParseRow i, CStr(dat(i, 1))
Next
r = dat
ActiveSheet.Columns(5).Style = "Percent"
End Sub
Sub ParseRow(rw As Long, s As String)
'Chair Leg Wood 100% 1m by 20cm
Dim i As Long
Dim sDim As String, sPCnt As String, sMat As String, sDesc As String
Dim sA As String, sB As String
i = InStr(s, "% ")
sDim = Trim(Replace(Mid(s, i + 2), " by ", " ")) ' text to right of %, remove "by"
sA = Trim(Left(sDim, InStr(sDim, " "))) ' split dimension string in two
sB = Trim(Mid(sDim, InStr(sDim, " ")))
s = Left(s, i)
i = InStrRev(s, " ")
sPCnt = Mid(s, i + 1) ' text back to first space before %
s = Trim(Left(s, i))
i = InStrRev(s, " ") ' last word in string
sMat = Mid(s, i + 1)
sDesc = Trim(Left(s, i)) ' whats left
dat(rw, 1) = sDesc
dat(rw, 2) = sMat
dat(rw, 3) = sA
dat(rw, 4) = sB
dat(rw, 5) = sPCnt
End Sub
First, I'd use the Split function to separate the parts into an array, this will avoid most of the string functions and string math:
Dim parts As Variant
parts = Split(A1)
Then, I'd do my comparisons to each part.
Finally, I'd concatenate the parts I didn't breakout, and place all parts on the sheet.
This is based on your example which has spaces inbetween every part, though something similar could work otherwise, you just have to do more work with each part.
Here's my stab at it. We could use about 10 more examples, but this should be a start. To use, select a one column range with your descriptions and run SplitProduct. It will split it out to the right of each cell.
Sub SplitProducts()
Dim rCell As Range
Dim vaSplit As Variant
Dim i As Long
Dim aOutput() As Variant
Dim lCnt As Long
Const lCOLDESC As Long = 1
Const lCOLMAT As Long = 2
Const lCOLPCT As Long = 3
Const lCOLREM As Long = 4
If TypeName(Selection) = "Range" Then
If Selection.Columns.Count = 1 Then
For Each rCell In Selection.Cells
'split into words
vaSplit = Split(rCell.Value, Space(1))
ReDim aOutput(1 To 1, 1 To 1)
'loop through the words
For i = LBound(vaSplit) To UBound(vaSplit)
Select Case True
Case IsPercent(vaSplit(i))
'percents always go in the same column
lCnt = lCOLPCT
If UBound(aOutput, 2) < lCnt Then
ReDim Preserve aOutput(1 To 1, 1 To lCnt)
End If
aOutput(1, lCnt) = vaSplit(i)
Case IsInList(vaSplit(i))
'list items always go in the same column
lCnt = lCOLMAT
ReDim Preserve aOutput(1 To 1, 1 To lCnt)
If UBound(aOutput, 2) < lCnt Then
ReDim Preserve aOutput(1 To 1, 1 To lCnt)
End If
aOutput(1, lCnt) = vaSplit(i)
Case IsMeasure(vaSplit(i))
'measurements go in the last column(s)
If UBound(aOutput, 2) < lCOLREM Then
lCnt = lCOLREM
Else
lCnt = UBound(aOutput, 2) + 1
End If
ReDim Preserve aOutput(1 To 1, 1 To lCnt)
aOutput(1, lCnt) = vaSplit(i)
Case Else
'everything else gets concatentated in the desc column
aOutput(1, lCOLDESC) = aOutput(1, lCOLDESC) & " " & vaSplit(i)
End Select
Next i
'remove any extraneous spaces
aOutput(1, lCOLDESC) = Trim(aOutput(1, lCOLDESC))
'write the values to the left of the input range
rCell.Offset(0, 1).Resize(1, UBound(aOutput, 2)).Value = aOutput
Next rCell
Else
MsgBox "Select a one column range"
End If
End If
End Sub
Function IsPercent(ByVal sInput As String) As Boolean
IsPercent = Right$(sInput, 1) = "%"
End Function
Function IsInList(ByVal sInput As String) As Boolean
Dim vaList As Variant
Dim vaTest As Variant
'add list items as needed
vaList = Array("Wood", "Glass", "Plastic")
vaTest = Filter(vaList, sInput)
IsInList = UBound(vaTest) > -1
End Function
Function IsMeasure(ByVal sInput As String) As Boolean
Dim vaMeas As Variant
Dim i As Long
'add measurements as needed
vaMeas = Array("mm", "cm", "m")
For i = LBound(vaMeas) To UBound(vaMeas)
'any number of characters that end in a number and a measurement
If sInput Like "*#" & vaMeas(i) Then
IsMeasure = True
Exit For
End If
Next i
End Function
No guarantees that this will be speedy on 10k rows.

Resources