VBA: How can I tokenize an input in a cell - vba

What I want to do is to convert time inputs in a cell to a specific format. Example:
"9" or "9 am" = 9:00:00 AM, which is the same as TIME(9, 0, 0)
"9 30" = 9:30:00 AM = TIME(9, 30, 0)
"4 30 pm" = 4:30:00 PM = TIME(16, 30, 0)
How can I achieve this in VBA?
Just a side note, this is actually my first time trying VBA.
Thanks.

I can support some learning:
Function timm(str As String) As Double
Dim spltstr() As String
Dim hr As Integer
Dim min As Integer
Dim sec As Integer
hr = 0
min = 0
sec = 0
str = Replace(str, """", "")
spltstr = Split(str)
hr = spltstr(0)
If UCase(spltstr(UBound(spltstr))) = "PM" Then hr = hr + 12
If 1 <= UBound(spltstr) Then
If IsNumeric(spltstr(1)) Then min = spltstr(1)
End If
timm = TimeSerial(hr, min, sec)
End Function
Place this in a module attached to the workbook. It can then be called as a function directly on the worksheet or from another sub.
It will change the text to a number so you will still need to apply a custom number format to the cell if using this as a UDF.
As per you comments to do it in place then you would use a Worksheet_Change event.
First use a custom format on the whole column that you desire like hh:mm:ss AM/PM. The put this in the worksheet code.
Private Sub Worksheet_Change(ByVal Target As Range)
On Error GoTo getout
Application.EnableEvents = False
If Not Intersect(Range("A:A"), Target) Is Nothing Then
If Not IsDate(Target.Value) Then
Target = timm(Target.Value)
End If
End If
Application.EnableEvents = True
Exit Sub
getout:
Application.EnableEvents = True
End Sub
It calls the prior code and returns the number. So as you leave edit mode it changes it to a time.

It looks like you want to use Split and TimeSerial. Here's an example to get you started.
Public Function ConvertToTime(ByVal Input_ As String) As Date
Dim vaSplit As Variant
Dim lHour As Long, lMinute As Long
'split the string into an array using space as a delimiter
vaSplit = Split(Input_, Space(1))
'The first element is the hour
lHour = vaSplit(0)
'If there's more than one element, the second element is the minute
If UBound(vaSplit) > 0 Then lMinute = vaSplit(1)
'if the last element is "pm", then add 12 to the hour
If vaSplit(UBound(vaSplit)) = "pm" Then lHour = lHour + 12
ConvertToTime = TimeSerial(lHour, lMinute, 0)
End Function

Related

Finding difference and Comparing dates in VBA

I am trying to write a code that takes in a date as input and checks whether or not it is later than the date defined in the code.
Sub times()
Dim d1 As Date
n = Application.InputBox("Enter a date in mm/dd/yyyy format: ")
entered_date = CDate(entered_date)
d1 = 5 / 11 / 2021
If d1 > entered_date Then
d2 = DateDiff("D", d1, entered_date)
MsgBox ("late by " & d2)
Else
MsgBox ("on time")
End If
End Sub
the date diff function doesn't seem to be working for me or something is wrong my logic.
thanks in advance!
Well here is my answer. And as #NicholasHunter says, Option Explicit is always better!
It is clear that you figure it out, but consider this answer, anyway hope to help someone else.
Option Explicit
Sub times()
'Here you can validate the format date type for your system...
Dim SystemDateType As Integer
Dim SysFormatDate As String
' 0 = m-d-y
' 1 = d-m-y
' 2 = y-m-d
If Application.International(xlDateOrder) = 0 Then
SystemDateType = 0
SysFormatDate = "mm/dd/yyyy"
ElseIf Application.International(xlDateOrder) = 1 Then
SystemDateType = 1
SysFormatDate = "dd/mm/yyyy"
ElseIf Application.International(xlDateOrder) = 2 Then
SystemDateType = 2
SysFormatDate = "yyyy/mm/dd"
End If
'Of course you can do this:
'SystemDateType = Application.International(xlDateOrder)
'Or just use a Select Case...
'But just want to be clear and set the variable SysFormatDate
Dim StopedByUser As String: StopedByUser = "StopedByUser"
'Here you can define your own message.
Dim d1 As Date
'Look for the DateSerial function
Dim d2 As Variant
'This could be Long, but just want to set a data type
Dim ErrHandler As Integer
Dim n As Variant
'Or maybe String?
Dim entered_date As Date
'alwayes respect Data Types...
RetryInput:
n = Application.InputBox("Enter a date in " & SysFormatDate & " format: ")
'The user input...
'Cancel...
If n = False Then
MsgBox StopedByUser
End
End If
'Error Handler!
If IsDate(n) Then 'If the user input a real date...
entered_date = CDate(entered_date)
d1 = DateSerial(2011, 11, 16) ' ==> YYYY, MM, DD
'It is always better to use DateSerial!
'And hard coded... Well hope is just your for the question.
'd1 = 5 / 11 / 2021
If d1 >= entered_date Then
'The >= and <= are better in this case...
d2 = DateDiff("D", d1, entered_date)
MsgBox ("late by " & Abs(d2)) 'Abs return the absolute value, instead -321 return 321.
Else
MsgBox ("on time")
End If
Else 'If the user don't know what is a date...
'ask the user... want to try again...
ErrHandler = MsgBox("Please enter a formated date (" & SysFormatDate & ")", vbDefaultButton1 + vbYesNo, "Retry")
If ErrHandler = 7 Then '7 is NO
MsgBox StopedByUser
End
ElseIf ErrHandler = 6 Then '6 is YES and go back in the code to...
GoTo RetryInput
End If
'Check for MSGBOX here: https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/msgbox-function
End If
End Sub

Setting a VBA form object using a string

Hello,
I am trying to set up a form which is a calendar from which the user can select a date (by default the current month appears). The form consists of 42 command buttons (I have left the default name ie. CommandButton1) which I am setting the day number.
At the moment I have a long-winded section of code for each button (I used Excel to generate this rather than type it all out) which locks and hides the button if it is outside of the month in question which looks like this:
NewDate.CommandButton1.Caption = Format(DATlngFirstMonth - DATintDayNumFirst + DATintX, "dd")
If DATintX < DATintDayNumFirst Then
With NewDate.CommandButton1
.Locked = True
.Visible = DATbooShowExtraDays
.ForeColor = RGB(150, 150, 150)
End With
Else
With NewDate.CommandButton1
.Locked = False
.Visible = True
.ForeColor = RGB(0, 0, 0)
End With
End If
I know that I can refer to a command button by:
Dim objCommandButton As Object
Set objCommandButton = NewDate.CommandButton1
..which neatens the code up somewhat. But what I would like to do is refer to the command button as a string so I can loop through all 42, ie.
Dim n as integer
n = 1
Do Until n > 42
Set objCommandButton = NewDate.CommandButton & n
'Some operations
n = n + 1
Loop
Many thanks in advance for assistance.
You can loop through all controls of the form. Try
Sub LoopButtons()
Dim it As Object
For Each it In NewDate.Controls
Debug.Print it.Name
Next it
End Sub
Then you can put conditional expression (if ... then) in place of Debug.Print or whatever. For example
If Instr(it.Name, "CommandButton") Then
'do your code
end if
Here's code which iterates over ActiveX controls on active sheet:
Sub IterateOverActiveXControlsByName()
Dim x As Integer
Dim oleObjs As OLEObjects
Dim ctrl As MSForms.CommandButton
Set oleObjs = ActiveSheet.OLEObjects
For x = 1 To 10
Set ctrl = oleObjs("CommandButton" & x).Object
Next
End Sub

VBA Dateadd Format - Need In Total Minutes

I have a userform in Microsoft Excel that I want to use as a stopwatch. However the format of "hh:mm" does not allow it to go above 23:59 as it goes back to 00:00
Private Sub SpinButton2_SpinUp()
If InsertEvent.TextBox1 = vbNullString Then
InsertEvent.TextBox1 = "00:00"
Else
InsertEvent.TextBox1.Value = Format(DateAdd("n", 1, InsertEvent.TextBox1.Value), "hh:mm")
'InsertEvent.TextBox1.Value = TimeValue("mm:ss")
'InsertEvent.TextBox1.Value = Format(InsertEvent.TextBox1.Value, "hh:mm")
End If
End Sub
Is there anyway to format this so that it can work as a clock of total minutes? Ideally I need it to go to about 125 minutes or so (125:00) but it doesn't matter if it is unlimited.
You can't use the built in Date/Time functions for this as you want a representation that is not a Date/Time.
Assuming you want to read the spinner value into the textbox:
Private Sub SpinButton2_SpinUp()
Dim minutes As Integer: minutes = Val(InsertEvent.SpinButton2.Value)
Dim hh As Integer: hh = minutes \ 60
Dim mm As Integer: mm = minutes - (hh * 60)
InsertEvent.TextBox1.Text = Format$(hh, "00") & ":" & Format$(mm, "00")
End Sub
To use a manually entered value from the textbox as the starting up/down point you need to re-parse "hh:mm" back to minutes, for example in the textbox Exit event:
Private Sub TextBox1_Exit(ByVal Cancel As MSForms.ReturnBoolean)
If (IsNumeric(TextBox1.Text)) Then
'// entering a number only assumes its minutes
SpinButton2.Value = TextBox1.Text
Exit Sub
End If
Dim hhmm() As String: hhmm = Split(TextBox1.Text, ":")
If (UBound(hhmm) = 1) Then
If (IsNumeric(hhmm(0)) And IsNumeric(hhmm(1))) Then
SpinButton2.Value = (hhmm(0) * 60) + hhmm(1)
Exit Sub
End If
End If
SpinButton2.Value = 0
End Sub
(Should add error checking for overflow/exceeding the spinners .Max property)

Report's textbox function call from ControlSource not firing

Firstly, here's a pic on my report in design mode:
The underlying query for the report returns values like so:
Allen Nelli 3:A,5:B,7:A,8:A, etc.
Breton Micheline 1:A,3:A,5:B,7:A, etc
Caporale Jody 1:A,3:A,5:B,7:A, etc
I had to use a subquery to get the third field which concatenates the number : letter combinations. These values actually represent day of month and designation to a particular shift in a schedule. So basically, for a given month, each individual works the designated shift indicated by the day value.
The intention is to call a user defined public function named PopulateTextboxes(Value as String) to be called from the first textbox in the report from the textbox's ControlSource property. The third field in the query is actually named Expr1 and that is being passed as a parameter to the function. The function is designed to populate all the textboxes with the appropriate letter designation: A or B or C or D, etc. The function itself is not being fired when I run the report.
The function is as follows:
Public Function PopulateTextboxes(Expr As String) As String
'Each element of Expr should be a number followed by a colon followed by a letter: 10:A,12:B,15:C, etc.
Dim shiftData() As String
Dim Data As Variant
Dim i As Integer
Dim j As Integer
Dim temp() As String
Dim txt As TextBox
Dim rpt As Report
Dim strCtrl As String
If Expr = "" Then Exit Function
If IsNull(Expr) Then Exit Function
shiftData = Split(Expr, ",")
If UBound(shiftData) > 0 Then
'Make a 2D array
ReDim Data(UBound(shiftData), 2)
'Load up 2D array
For i = 0 To UBound(shiftData) - 1
If shiftData(i) <> "" Then
temp = SplitElement(shiftData(i), ":")
Data(i, 0) = temp(0)
Data(i, 1) = temp(1)
End If
Next i
Set rpt = Reports.item("Multi_Locations_Part_1")
If UBound(days) = 0 Then
MsgBox "days array not populated"
Exit Function
End If
'Populate each Textbox in the Multi_Locations_Part_1 Report
For i = 1 To UBound(days)
strCtrl = "txtDesig_" & CStr(i)
Set txt = rpt.Controls.item(strCtrl)
For j = 0 To UBound(Data) - 1
If Data(j, 0) = days(i) Then
txt.Value = Data(j, 1) 'A,B,C,etc.
Exit For
End If
Next j
Next i
End If
PopulateTextboxes = Expr
End Function
Private Function SplitElement(Value As String, Delim As String) As String()
Dim result() As String
result = Split(Value, Delim)
SplitElement = result
End Function
Please advise.
The best way is to call your function from the Format event of the Detail section, so it will be called for each record.
Private Sub Detail_Format(Cancel As Integer, FormatCount As Integer)
Call PopulateTextboxes(Me.Expr1)
End Sub
If PopulateTextboxes is in a separate module, I suggest to pass Me as additional parameter for the report, so you don't have to hardcode the report name.
Also note that you need the Set keyword when assigning object variables, e.g.
Set txt = rpt.Controls.item("txtDesig_" & CStr(i))

Excel VBA - Custom Function; #VALUE error; VLOOKUP on different worksheet

I am attempting to do a VLOOKUP on a different worksheet based on given parameters in the function. I've played around with it for several hours and can not figure out why it is not working. I cut down the code as much as I could to test, but am unable to effectively find a solution. I think it might be an issue of how I am calling the range from the other worksheet for the VLOOKUP. Code is below. Please advice. If I'm unclear about what I'm asking just ask and I will provide feedback. Thank you
Function GraphDataA(cR As String, time As String, aClient As String, tps As String, dat As String)
Dim client As Boolean
Dim day As Boolean
Dim tot As Boolean
Dim dayTotData As Range
Dim dayTotDatas As Worksheet
Set dayTotDatas = ActiveWorkbook.Sheets("DayTot")
Set dayTotData = dayTotDatas.Range("A3:AI168")
client = False
day = False
tot = False
If date = "" Then
GraphDataA = ""
End If
If aClient = "" Then
GraphDataA = ""
End If
If cR = "Client" Then
client = True
End If
If time = "Day" Then
day = True
End If
If tps = "Total" Then
tot = True
End If
If client = True Then
If day = True Then
If tot = True Then
GraphDataA = WorksheetFunction.VLookup(aClient, dayTotData, WorksheetFunction.Match(dat, dayDate, 0) + 8, _
False)
End If
End If
End If
End Function
VLOOKUP() will throw an error if nothing matches. So you need to add error catching code to your function.
You need to modify the function as
Function MyFunction() as Something
On Error Goto ErrorHandler
' Your existing code goes here
Exit Function
ErrorHandler:
MyFunction = -1 ' Or something which indicates that the value isn't found
End Function
You don't appear to be returning any value from your function. Try adding As Variant to the end of the first line like so:
Function GraphDataA(cR As String, time As String, aClient As String, tps As String, dat As String) As Variant