UDF gives #Value Error in Spreadsheet

ebraatz

Board Regular
Joined
Feb 25, 2017
Messages
76
I'm making a workbook to track student attendance for the year. There is an identical tab for each month of the year which has a linear calendar that will change depending on the year chosen. At the end of the month I tally the number of Absences, etc for a given term. The terms begin and end in the middle of a month, so i wrote a UDF, Function CountPeriod1, to determine the range to use for CountIf based on the given date range and the start and end dates of the period. The function works perfectly when run with a test sub, but when I use it in the worksheet I get a #Value error. I've read many forums about this problem but none of those situations help me. The Function code is:

Code:
Function CountPeriod1(StartDate As Range, EndDate As Range, DateRange As Range, _
                        CountRange As Range, CountWhat1 As Variant, Optional CountWhat2 As Variant, _
                        Optional CountWhat3 As Variant) As Long
                        
    Dim FirstDate As Range
    Dim LastDate As Range
    Dim MatchED As Range
    Dim MatchSD As Range
    Dim PeriodRange As Range
    
    Dim m As Long
    Dim r As Long
           
    
    If DateRange.Rows.Count > 1 Then
        MsgBox "DateRange must be a singular row"
    End If
    
    Set FirstDate = Cells(DateRange.Row, DateRange.Columns(1).Column)                               'First day of selected DateRange
    Set LastDate = DateRange.SpecialCells(xlCellTypeLastCell)           'Last day of selected DateRange
    
    If IsEmpty(LastDate) = True Then
        m = DateRange.Find(What:="*", LookIn:=xlValues, searchorder:=xlByColumns, searchdirection:=xlByColumns).Column
                
        Set LastDate = Cells(DateRange.Row, m)
    End If
    
    'Find where the Period starts and ends in relation to the selected DateRange
    Select Case StartDate.Value
        
        Case Is < FirstDate.Value               'Period begins before the selected DateRange begins
            
            Select Case EndDate.Value
                    
                Case Is > FirstDate.Value       'Period Ends after selected DateRange begins
                    
                    Select Case EndDate.Value
                            
                        Case Is > LastDate.Value    'Period starts before and ends after selected DateRange
                                
                            Set PeriodRange = CountRange
                                
                        Case Is < LastDate.Value    'Period begins before daterange starts, but ends before DateRange ends
                                
                            m = Application.Match(CLng(CDate(EndDate.Value)), DateRange, 0)
                            Set MatchED = Cells(DateRange.Row, DateRange.Columns(m).Column)
                                
                            Set PeriodRange = Range(Cells(CountRange.Row, FirstDate.Column), Cells(CountRange.Row, MatchED.Column))
                             
                        Case Is = LastDate.Value    'Period begins before daterange starts and ends on last day of DateRange
                                
                            Set PeriodRange = CountRange
                                
                    End Select
                
                Case Is < FirstDate.Value   'Period Ends before selected daterange begins
                    
                    CountPeriod1 = 0
                    
                Case Is = FirstDate.Value   'Period ends on first day of Daterange
            
                    Set PeriodRange = Cells(CountRange.Row, FirstDate.Column)
                        
            End Select
            
        Case Is > FirstDate.Value               'Period begins after the selected DateRange begins
        
            Select Case StartDate.Value
            
                Case Is > LastDate.Value        'Period begins after daterange ends
                
                    CountPeriod1 = 0
            
                Case Is < LastDate.Value        'Period begins after start, but before end of DateRange
                
                    m = Application.Match(CLng(CDate(StartDate.Value)), DateRange, 0)
                    Set MatchSD = Cells(DateRange.Row, DateRange.Columns(m).Column)
                    
                    Select Case EndDate.Value
                        
                        Case Is < LastDate.Value    'Period ends before the DateRange Ends - Period is contained entirely within daterange
                                                
                            m = Application.Match(CLng(CDate(LastDate.Value)), DateRange, 0)
                            Set MatchED = Cells(DateRange.Row, DateRange.Columns(m).Column)
                            
                            Set PeriodRange = Range(Cells(CountRange.Row, MatchSD.Column), Cells(CountRange.Row, MatchED.Column))
                        
                        Case Is > LastDate.Value    'period ends after DateRange Ends
                            
                            Set PeriodRange = Range(Cells(CountRange.Row, MatchSD.Column), Cells(CountRange.Row, LastDate.Column))
                                                    
                        Case Is = LastDate.Value    'Period ends on last day of date range
                        
                            Set PeriodRange = Range(Cells(CountRange.Row, MatchSD.Column), Cells(CountRange.Row, LastDate.Column))
                        
                    End Select
                
                Case Is = LastDate.Value        'Period begins on last day of DateRange
                    
                    Set PeriodRange = Cells(CountRange.Row, LastDate.Column)
                
            End Select
            
        Case Is = FirstDate.Value               'Period begins on the first day of selected DateRange
        
            Select Case EndDate.Value
            
                Case Is < LastDate.Value        'Period ends before last day of daterange
                
                    m = Application.Match(CLng(CDate(EndDate.Value)), DateRange, 0)
                    Set MatchED = Cells(DateRange.Row, DateRange.Columns(m).Column)
                    
                    Set PeriodRange = Range(Cells(CountRange.Row, FirstDate.Column), Cells(CountRange.Row, MatchED.Column))
                                    
                Case Is > LastDate.Value        'Period ends after last day of daterange
                
                    Set PeriodRange = CountRange
                
                Case Is = LastDate.Value        'period ends on last day of daterange
                
                    Set PeriodRange = CountRange
                
            End Select
    End Select
    
    If IsMissing(CountWhat3) = True Then
        If IsMissing(CountWhat2) = True Then
            CountPeriod1 = Application.WorksheetFunction.CountIf(PeriodRange, CountWhat1)
            Else:
                CountPeriod1 = Application.WorksheetFunction.CountIf(PeriodRange, CountWhat1) _
                                + Application.WorksheetFunction.CountIf(PeriodRange, CountWhat2)
        End If
        Else:
            CountPeriod1 = Application.WorksheetFunction.CountIf(PeriodRange, CountWhat1) _
                                + Application.WorksheetFunction.CountIf(PeriodRange, CountWhat2) _
                                + Application.WorksheetFunction.CountIf(PeriodRange, CountWhat3)
                                
    End If
    
    
                       
End Function

The function is entered into the cell as:

=CountPeriod1(FirstDayPK, LastDayP1,AugSeptDates,E7:AH7, "A")

TIA!!
 
Last edited:
So I'm not sure what happened, but I'm not getting the correct answer through the test sub anymore. I've updated the Cells and Range references to the DateRange.Worksheet.Cells as suggested and fixed the Find Search order.

The Find method seems to be the hickup. I have no idea where it's coming up with it's answer. There are 30 columns in the DateRange, all with a formula in them, with the last date shown in column 23. Using the search order xlprevious, the function returns 5 for the value of m. I have no idea where that might come from. I really want to post a screen shot, but I'm not allowed. What am I missing?
 
Upvote 0

Excel Facts

Easy bullets in Excel
If you have a numeric keypad, press Alt+7 on numeric keypad to type a bullet in Excel.
If i've understood correctly you have a horizontal range, named (set) as DateRange, containing formulas that either return a date or an empty string and you want to find the first and the last cell with a date. If so, try something like this

Code:
Dim FirstDate As Range, LastDate As Range

With DateRange
    Set FirstDate = .Find("*", After:=.Cells(.Columns.Count), _
        LookIn:=xlValues, SearchOrder:=xlByColumns, SearchDirection:=xlNext)
    Set LastDate = .Find("*", After:=.Cells(1), _
        LookIn:=xlValues, SearchOrder:=xlByColumns, SearchDirection:=xlPrevious)
End With

If Not FirstDate Is Nothing Then MsgBox FirstDate.Address
If Not LastDate Is Nothing Then MsgBox LastDate.Address

M.
 
Upvote 0
If i've understood correctly you have a horizontal range, named (set) as DateRange, containing formulas that either return a date or an empty string and you want to find the first and the last cell with a date.

Marcelo,

That is essentially correct. The first date in the Daterange will always be in the first cell, but depending on the way the calendar falls, the lastdate in the daterange could move as it only fills M-F and skips the weekends. My code for that section now looks like this:

Code:
    Set FirstDate = DateRange.Worksheet.Cells(DateRange.Row, DateRange.Columns(1).Column)          'First day of selected DateRange
    r = DateRange.Columns.Count
    Set LastDate = DateRange.Worksheet.Cells(DateRange.Row, DateRange.Columns(r).Column)           'Last day of selected DateRange
    
    If IsEmpty(LastDate) = True Then
        m = DateRange.Find(What:="*", After:=DateRange.Cells(1), LookIn:=xlValues, searchorder:=xlByColumns, searchdirection:=xlPrevious).Column
                
        Set LastDate = DateRange.Worksheet.Cells(DateRange.Row, m)
    End If
    
    MsgBox LastDate.Address

The DateRange is $E$6:$AH$6. (total 30 columns) The last Date shown is in AA6. (column 23 in relation to the DateRange)

When I run my test sub:

Code:
Sub test()


MsgBox CountPeriod1(Range("FirstDayPK"), Range("lastdayp1"), Range("AugSeptDates"), Range("ROAugSept").Rows(1), "A", "T", "P")


End Sub

The If statement should be triggered and shows True=True when I hover over the statement.
'm' is calculating as 5 and even with that, the Set LastDate statement isn't correct. If I hover over that statement
Code:
Set LastDate = DateRange.Worksheet.Cells(DateRange.Row, m)

the right side of the statement will give me the column E ,which would be expected and which also happens to be the start date, but when I run the sub it sill tells me that LastDate = $AH$6.

I'm not sure what's going on there. :confused:

If it matters, there is a different formula in the first cell of the DateRange than in the others. It is =FirstDayK3.
All the other cells have the formula I shared before.
 
Upvote 0
If i've understood correctly you have a horizontal range, named (set) as DateRange, containing formulas that either return a date or an empty string and you want to find the first and the last cell with a date. If so, try something like this

Code:
Dim FirstDate As Range, LastDate As Range

With DateRange
    Set FirstDate = .Find("*", After:=.Cells(.Columns.Count), _
        LookIn:=xlValues, SearchOrder:=xlByColumns, SearchDirection:=xlNext)
    Set LastDate = .Find("*", After:=.Cells(1), _
        LookIn:=xlValues, SearchOrder:=xlByColumns, SearchDirection:=xlPrevious)
End With

If Not FirstDate Is Nothing Then MsgBox FirstDate.Address
If Not LastDate Is Nothing Then MsgBox LastDate.Address

M.

So I copied and pasted this code into mine and it works fine. Can you explain to me how your code is different than mine? To me they look to do the same thing, but I am a novice. :confused:
 
Upvote 0
Why are you setting LastDate to the last cell of the range and, after, checking if it is empty etc etc?
Try the code i suggested above to get directly the last cell that contains a date.

M.
 
Upvote 0
So I copied and pasted this code into mine and it works fine. Can you explain to me how your code is different than mine? To me they look to do the same thing, but I am a novice. :confused:

To understand take a look at
Find Method [Excel 2003 VBA Language Reference]

You can see what the parameters After and SearchDirection, among others, mean.
In short, to get the last cell with a date (not blank) we set After:= first_cell of the range and SearchDirection:= xlPrevious (backward search).
On the other hand, to get the first cell with a date (not blank) we set After:=last_cell of the range and SearchDirection:=xlNext (forward search)

M.
 
Upvote 0
Thank You all for your help. If anyone is interested, here is my fully functioning code:

Code:
Function CountPeriod(StartDate As Range, EndDate As Range, DateRange As Range, _
                        CountRange As Range, CountWhat1 As Variant, Optional CountWhat2 As Variant, _
                        Optional CountWhat3 As Variant) As Long
                        
    Dim FirstDate As Range
    Dim LastDate As Range
    Dim MatchED As Range
    Dim MatchSD As Range
    Dim PeriodRange As Range
    
    Dim m As Long
    Dim r As Long
           
    
    If DateRange.Rows.Count > 1 Then
        MsgBox "DateRange must be a singular row"
    End If
    
    Set FirstDate = DateRange.Worksheet.Cells(DateRange.Row, DateRange.Columns(1).Column)          'First day of selected DateRange
       
    With DateRange                                                      'Find last date in DateRange
        Set LastDate = .Find("*", After:=.Cells(1), _
            LookIn:=xlValues, SearchOrder:=xlByColumns, SearchDirection:=xlPrevious)
    End With


    
    'Find where the Period starts and ends in relation to the selected DateRange
    Select Case StartDate.Value
        
        Case Is < FirstDate.Value               'Period begins before the selected DateRange begins
            
            Select Case EndDate.Value
                    
                Case Is > FirstDate.Value       'Period Ends after selected DateRange begins
                    
                    Select Case EndDate.Value
                            
                        Case Is > LastDate.Value    'Period starts before and ends after selected DateRange
                                
                            Set PeriodRange = CountRange
                                
                        Case Is < LastDate.Value    'Period begins before daterange starts, but ends before DateRange ends
                                
                            m = Application.Match(CLng(CDate(EndDate.Value)), DateRange, 0)
                            Set MatchED = Cells(DateRange.Row, DateRange.Columns(m).Column)
                                
                            Set PeriodRange = DateRange.Worksheet.Range(Cells(CountRange.Row, FirstDate.Column), Cells(CountRange.Row, MatchED.Column))
                             
                        Case Is = LastDate.Value    'Period begins before daterange starts and ends on last day of DateRange
                                
                            Set PeriodRange = CountRange
                                
                    End Select
                
                Case Is < FirstDate.Value   'Period Ends before selected daterange begins
                    
                    CountPeriod1 = 0
                    
                Case Is = FirstDate.Value   'Period ends on first day of Daterange
            
                    Set PeriodRange = DateRange.Worksheet.Cells(CountRange.Row, FirstDate.Column)
                        
            End Select
            
        Case Is > FirstDate.Value               'Period begins after the selected DateRange begins
        
            Select Case StartDate.Value
            
                Case Is > LastDate.Value        'Period begins after daterange ends
                
                    CountPeriod1 = 0
            
                Case Is < LastDate.Value        'Period begins after start, but before end of DateRange
                
                    m = Application.Match(CLng(CDate(StartDate.Value)), DateRange, 0)
                    Set MatchSD = DateRange.Worksheet.Cells(DateRange.Row, DateRange.Columns(m).Column)
                    
                    Select Case EndDate.Value
                        
                        Case Is < LastDate.Value    'Period ends before the DateRange Ends - Period is contained entirely within daterange
                                                
                            m = Application.Match(CLng(CDate(LastDate.Value)), DateRange, 0)
                            Set MatchED = DateRange.Worksheet.Cells(DateRange.Row, DateRange.Columns(m).Column)
                            
                            Set PeriodRange = DateRange.Worksheet.Range(Cells(CountRange.Row, MatchSD.Column), Cells(CountRange.Row, MatchED.Column))
                        
                        Case Is > LastDate.Value    'period ends after DateRange Ends
                            
                            Set PeriodRange = DateRange.Worksheet.Range(Cells(CountRange.Row, MatchSD.Column), Cells(CountRange.Row, LastDate.Column))
                                                    
                        Case Is = LastDate.Value    'Period ends on last day of date range
                        
                            Set PeriodRange = DateRange.Worksheet.Range(Cells(CountRange.Row, MatchSD.Column), Cells(CountRange.Row, LastDate.Column))
                        
                    End Select
                
                Case Is = LastDate.Value        'Period begins on last day of DateRange
                    
                    Set PeriodRange = DateRange.Worksheet.Cells(CountRange.Row, LastDate.Column)
                
            End Select
            
        Case Is = FirstDate.Value               'Period begins on the first day of selected DateRange
        
            Select Case EndDate.Value
            
                Case Is < LastDate.Value        'Period ends before last day of daterange
                
                    m = Application.Match(CLng(CDate(EndDate.Value)), DateRange, 0)
                    Set MatchED = DateRange.Worksheet.Cells(DateRange.Row, DateRange.Columns(m).Column)
                    
                    Set PeriodRange = DateRange.Worksheet.Range(Cells(CountRange.Row, FirstDate.Column), Cells(CountRange.Row, MatchED.Column))
                                    
                Case Is > LastDate.Value        'Period ends after last day of daterange
                
                    Set PeriodRange = CountRange
                
                Case Is = LastDate.Value        'period ends on last day of daterange
                
                    Set PeriodRange = CountRange
                
            End Select
    End Select
    
    If IsMissing(CountWhat3) = True Then
        If IsMissing(CountWhat2) = True Then
            CountPeriod1 = Application.WorksheetFunction.CountIf(PeriodRange, CountWhat1)
            Else:
                CountPeriod1 = Application.WorksheetFunction.CountIf(PeriodRange, CountWhat1) _
                                + Application.WorksheetFunction.CountIf(PeriodRange, CountWhat2)
        End If
        Else:
            CountPeriod1 = Application.WorksheetFunction.CountIf(PeriodRange, CountWhat1) _
                                + Application.WorksheetFunction.CountIf(PeriodRange, CountWhat2) _
                                + Application.WorksheetFunction.CountIf(PeriodRange, CountWhat3)
                                
    End If
    
    
                       
End Function
 
Upvote 0
Yes, Marcelo. Working great. I had to add the line:

Code:
If PeriodRange Is Nothing Then

CountPeriod = 0

End If

I though I had that covered in the Select Case lines (There are two situations where CountPeriod = 0)
So I'm not sure why that didn't work right. I'd love any insight on that if you're up to it, but the line above has fixed my problem and It's working great.
 
Last edited:
Upvote 0
I did not analyze all your code. Only the part about how to set LastDate. If you still have questions about your code, it might be better to create a new Thread - this is getting very long.

M.
 
Upvote 0

Forum statistics

Threads
1,215,465
Messages
6,124,980
Members
449,201
Latest member
Lunzwe73

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top