VBA Basics - Calling Function From a Sub - vba

I want to write a Function Macro that calculates the days between two dates. It looks like I have that.
Function daysRem(today As Date, eoc As Date) As Integer
daysRem = Abs(DateDiff("d", today, eoc))
End Function
Now, I need to "Call" that function from a Sub to roughly estimate the number of weeks remaining. I'd like that to be in a message box. That's where I've hit about 2 hours of frustration. This has got to be simple, but I just can't figure out what to do.

Try this:
Private Sub DisplayDaysDiff()
Dim dtDate1 as Date
Dim dtDate2 as Date
dtDate1 =ThisWorkbook.Worksheets("Sheet1").Range("A2")
dtDate2=ThisWorkbook.Worksheets("Sheet1").Range("B2")
MsgBox "Days difference: " & CStr(daysRem(dtDate1, dtDate2)), vbInformation
End Sub
Call it from a button_click event

Sub Main
x = MyFunction(Param1)
End Sub
MyFunction(MyDate as Date)
MyFunction = DateDiff("ww", Date(), MyDate)
End Function
You assign the value to the function name. The inbuilt Date() function is todays date.
Functions return something. Subs don't. Macros ARE SUBS.
Functions return something.
So
Function Test
Test = 5
End Function
Sub Main
x = Test() + 5
End Sub
So x = 10.
Sub Main
Param1 = #2/2/2017#
Range("B4").Value = MyFunction(Param1)
End Sub
MyFunction(MyDate as Date)
MyFunction = DateDiff("ww", Date(), MyDate)
End Function
Date is both a data type and a function. One doesn't have brackets and one does.

Related

excel vba Ubound/Lbound error

UBound seems to not be returning anything. I am using the Pricer function in excel and passing it a column. Did I mismatch my data types? I UBound on a dummy array that accessed arrP.Value and that didnt work either. Thoughts?
Function Pricer(arrP As Variant) As Double
sd = Application.WorksheetFunction.StDevP(arrP)
avg = Application.WorksheetFunction.Average(arrP)
PriceUB = avg + sd
PriceLB = avg - sd
MsgBox UBound(aarP)
Pricer = Application.WorksheetFunction.Average(arrP)
End Function
Option Explicit is exactly what would save you the next time you are suffering from "fat fingers". Just do not forget to declare the other variables:
Option Explicit
Public Sub TestMe()
MsgBox Pricer(Array(1, 2, 3, 4, 5, 100))
End Sub
Function Pricer(arrP As Variant) As Double
Dim sd, avg, PriceUB, PriceLB
sd = Application.WorksheetFunction.StDevP(arrP)
avg = Application.WorksheetFunction.Average(arrP)
PriceUB = avg + sd
PriceLB = avg - sd
MsgBox UBound(arrP)
Pricer = Application.WorksheetFunction.Average(arrP)
End Function
To use the formula in Excel worksheet, pass it like this:
The semicolon could be a comma, depending on your language settings:
To make the Pricer() work with Range() as an Excel formula this is what to do:
Function Pricer(arrP As Range) As Double
MsgBox arrP.Cells.Count
Pricer = Application.WorksheetFunction.Average(arrP)
End Function

How to find date using day number of the year in VBA?

Suppose If i enter 209. It has to give me Jul 28th.
I could get day number from the date but in reverse. Can someone help me with this?
May be this code will help you
Sub Test()
Dim x
x = 209
MsgBox DateAdd("d", x - 1, "2018/1/1")
End Sub
Just add the number of days to the last day of last year.
=209 +"12/31/2017"
The following
=DATE(2018,1,209)
You can format in different ways the date returned e.g.
=TEXT(DATE(2018,1,209),"Mmm dd yyyy")
VBA and UDF:
Option Explicit
Public Sub test()
Debug.Print GetDate(209)
End Sub
Public Function GetDate(ByVal daynumber As Long) As String
GetDate = Format$(DateSerial(Year(Date), 1, daynumber), "Mmm dd yyyy")
End Function

Using Word VBA, How to change the condition of a loop without having to replicate the whole loop?

I want to call a routine with arguments to be used as the condition of a loop.
The code that follows doesn't works, it gives the ERROR 13 (incompatible types). How do I fix this? Many thanks!
Sub foo()
Call bar("a>10")
End Sub
Sub bar(myCondition as String)
Dim a as Integer
Do
Debug.Print a
a=a+1
Loop Until myCondition
End Sub
Word has no equivalent to Excel's Evaluate so this is only possible the "long way around". You need to figure out every variation you want to pass for evaluation, test for which it is, and then run the correct type of loop. Seems to me you'd need to do something like this, anyway, since using the test you have in your example wouldn't loop for "<" (assuming you use positive integers).
In order to avoid repeating the code you want to execute in the loop, put that in a separate function.
Sub foo()
Call bar(">", 10)
End Sub
Sub bar(ByVal conditionOperator As String, ByVal conditionValue As Long)
Dim a As Long
Select Case conditionOperator
Case Is = "<"
Do
a = PerformLoop(a)
Loop While a < conditionValue
Case Is = ">"
Do
a = PerformLoop(a)
Loop Until a > conditionValue
Case Is = "="
Do
a = PerformLoop(a)
Loop Until a = conditionValue
Case Is = ">="
Do
a = PerformLoop(a)
Loop Until a >= conditionValue
Case Is = "<="
Do
a = PerformLoop(a)
Loop While a < conditionValue
End Select
End Sub
Function PerformLoop(a As Long) As Long
Debug.Print a
a = a + 1
PerformLoop = a
End Function

Modify variable using a sub/function

I am trying to add a + sign in front of certain variables if they are positive.
ex:
Sub mySub()
Dim cash As Variant
End Sub
It works well if I do:
Dim plus As String
plus = "+"
If cash > 0 Then
cash = plus & cash
Else
cash= cash
End If
But I was looking for a sub or function that would take all my variables and add a + sign in front of them if they are positive.
sub NewSub(i As Variant)
If i > 0 Then
i = plus & i
Else
i = i
End If
End sub
But it doesn't seem to work as it doesn't show me anything (I then display my variables in a cell in excel). And a function doesn't work either.
Any ideas on how to create a sub/function to do that? Can I loop through my variables in any way ?
First off, start using Option Explicit which forces you to explicitly declare every variable and will catch mismatch errors in the VBA editor and not at runtime.
Next, if you are going to change a numerical variable to a string by prefacing a 'plus' sign onto the left end then the original variable will have to be a variant type. If you want to pass a parameter into a sub procedure and have the sub change it then the parameter must be ByRef.
Alternately, you could push the variable into a function and return the new value.
Option Explicit
Sub main()
Dim cash As Variant, cash2 As Variant
cash = 10
Debug.Print cash '10 (as number)
AddPlus cash
Debug.Print cash '+10 (as string)
cash = 10
Debug.Print cash '10 (as number)
cash = udfAddPlus(cash)
Debug.Print cash '+10 (as string)
End Sub
Sub AddPlus(ByRef i As Variant)
If i > 0 Then
i = "+" & i
Else
i = i
End If
End Sub
Function udfAddPlus(i As Variant)
If i > 0 Then
udfAddPlus = "+" & i
Else
udfAddPlus = i
End If
End Function
The Debug.Print command sends output to the VBE's Immediate window.

Date Validation not working correctly

I am trying to validate a date that is entered into a textbox on a user form. When I enter for example 23/7/15 the code works fine and gives me a value as the date entered is valid, when I enter 32/7/2015 I get a message that I program telling the user that they have entered a wrong date but if I enter 32/7/15 it sets the date in the code to some date in 1932 and as this is a valid date it does not throw the error. Below is the code that I am using. Is there anyway to validate 32/7/15?
Private Function errorCheckingDate(box1, message) As Boolean '<============= Returns a True or a False
If Not IsDate(box1) Then '<============================================= Checks to see if entered value is date or not
MsgBox message, vbExclamation, "Invalid Selection" '<=============== Displays a messgae box if not date
box1.SetFocus '<==================================================== Puts cursor back to the offending box
errorCheckingDate = True '<========================================= Sets function to True
End If
End Function
box1 is just the value of the textbox once it has been converted to a date. Below is the conversion
secondSelectedStr = Format(DateTextBox.value, "dd-mm-yy") '<===== Convert to correct format
Any help would be great.
In addition to the ordering issue, VBA will also try to adapt invalid date ranges to a proper date. For example, this has always been a neat technique to get the X day of the year:
d = DateSerial(2015, 1, X)
X can be really any number here (up to the limits of the data type, of course). If X is 500, it will produce the date 2016-5-14. So the mm/dd vs dd/mm vs yy/mm/dd issue plus the fact that VBA accepts numbers outside valid ranges are both troublesome.
Without range-checking each date component (where you have to consider leap years and whatnot), I think the best solution is to just have VBA create your date and then test it to ensure it's what you're expecting. For example:
' Get the date components (assuming d/m/yy)...
Dim a As Variant
a = Split(strDate, "/")
' Need 3 components...
If UBound(a) <> 2 Then MsgBox "Invalid date": Exit Sub
' Create the date. This will hardly ever fail.
' VBA will create some kind of date using whatever numbers you throw at it.
Dim d As Date
d = DateSerial(a(2), a(1), a(0))
' Make sure the date VBA created matches what we expected...
If Day(d) <> CInt(a(0)) Then MsgBox "Invalid day": Exit Sub
If Month(d) <> CInt(a(1)) Then MsgBox "Invalid month": Exit Sub
If Right$(Year(d), 2) <> a(2) Then MsgBox "Invalid year": Exit Sub
You could throw that into a subroutine (you may have noticed the Exit Sub statements) to validate the date that gets entered. The TextBox_Exit event would work well. That way, you could set Cancel = True to prevent the user from leaving the textbox with an invalid date.
Thanks for the answers guys but I need to accommodate for leap years and other things. As the isdate function works if the date is entered in the form 1/1/2015 I have just used the len() function to make sure that the date entered is of a certain length. Below is the code that I used. Thanks for you help once again. :)
Private Function errorCheckingDate(box1, message) As Boolean '<============= Returns a True or a False
If Not IsDate(box1) Or Len(box1) < 8 Or Len(box1) > 10 Then '<============================================= Checks to see if entered value is date or not
MsgBox message, vbExclamation, "Invalid Selection" '<=============== Displays a messgae box if not date
box1.SetFocus '<==================================================== Puts cursor back to the offending box
errorCheckingDate = True '<========================================= Sets function to True
End If
End Function
Maybe instead of using IsDate you could use your own custom function likened to it.
Function isValidDate(d as String) as Boolean
Dim parts as Variant
Dim months29 as variant
Dim months30 as variant
Dim months31 as variant
months29 = Array(2)
months30 = Array(4, 6, 9, 11)
months31 = Array(1, 3, 5, 7, 8, 10, 12)
parts = Split(d, "-")
if parts(2) mod 4 = 0 then
daysLeapYear = 28
else:
daysLeapYear = 29
end if
if ((IsInArray(parts(0), months29) and parts(1) <= daysLeapYear) or _
(IsInArray(parts(0), months30) and parts(1) < 31) or _
(IsInArray(parts(0), months31) and parts(1) < 32)) and _
parts(2) < 16 then
isValidDate = True
else:
isValidDate = False
end if
end function
Function IsInArray(stringToBeFound As String, arr As Variant) As Boolean
IsInArray = (UBound(Filter(arr, stringToBeFound)) > -1)
End Function
This should cover most cases. Not sure how detailed you feel you need to be. IsInArray courtesy of #JimmyPena.