VBA Class Module: Should it be a property?

AFPathfinder

Well-known Member
Joined
Apr 8, 2012
Messages
533
Office Version
  1. 365
  2. 2016
Platform
  1. Windows
I'm trying to learn object classes in VBA and having a hard time trying to apply the knowledge; looking for a spot check.

This is working as I intended, but I don't think I'm doing the property "CrossesFY" the right way. Here's the clsCEDOrder class:
VBA Code:
Option Explicit
'CLASS MODULE - clsCEDOrder

'Member variables
Private m_FY As Integer
Private m_Length As Integer
Private m_CrossFY As Boolean
Private m_ProcDate As Date

'Properties
Property Get TDYLength() As Integer
    TDYLength = m_Length
End Property
Property Let TDYLength(ByVal intLen As Integer)
    m_Length = intLen
End Property

Property Get CrossesFY() As Boolean
    FYCrossCheck
    CrossesFY = m_CrossFY
End Property
Private Property Let CrossesFY(ByVal blnXFY As Boolean)
    m_CrossFY = blnXFY
End Property

Property Get ProceedDate() As Date
    ProceedDate = m_ProcDate
End Property
Property Let ProceedDate(ByVal dtProc As Date)
    m_ProcDate = dtProc
End Property

'Methods
Private Sub FYCrossCheck()
    'Sets m-CrossFY bool value if a fiscal year change occurs between start and stop dates.

    Dim intStartFY As Integer
    Dim intStopFY As Integer
   
    'If either proceed date or TDY length is blank or if the two values aren't recognized, set false and exit.
    If m_ProcDate = 0 Or m_Length = 0 Or IsDate(m_ProcDate) = False Or IsNumeric(m_Length) = False Then
        m_CrossFY = False
        Exit Sub
    End If
   
    'ID the start and stop fiscal year values.
    intStartFY = FY(DateValue(m_ProcDate))
    intStopFY = FY(DateValue(m_ProcDate) + m_Length)
   
    'Compare and let result.
    If intStartFY <> intStopFY Then
        m_CrossFY = True
    Else
        m_CrossFY = False
    End If
End Sub
Private Function FY(Optional ByVal dt As Date) As Long
    'Returns the 4 character FY value of a given date.
   
    'Set default value if no date was given.
    If dt = 0 Then
        dt = Date
    End If
   
    'Set FY to the month of the provided date.
    Select Case Month(dt)
        Case 10 To 12
            FY = Year(dt) + 1
        Case Else
            FY = Year(dt)
    End Select
End Function

Here's the test sub:
VBA Code:
Option Explicit

Sub CEDOrder_Test()
    Dim ord As clsCEDOrder
   
    Set ord = New clsCEDOrder
   
    ord.ProceedDate = "4/20/2024"
    ord.TDYLength = 183
    Debug.Print ord.CrossesFY
End Sub

I feel like this basically bypasses the Private Property Let CrossesFY(ByVal blnXFY As Boolean) which leads me to think I'm not doing it right.

Again, it's testing fine. I just want to know if I need to think about this differently before I go and class my world.

Should CrossesFY even be a property? Maybe just a public function? Taking all feedback!
 
Last edited:

Excel Facts

What is the shortcut key for Format Selection?
Ctrl+1 (the number one) will open the Format dialog for whatever is selected.
There are a couple of errors in this code, even though it returns the expected results.
Let's start with the CEDOrder_Test procedure.
By passing the date as text you expose yourself to implicit type conversion. In the US, writing the date “4/5/2024” will return April 5 , while in GB (and other locations) it may return May 4. What consequences this will have when you pass a “date” of 4/12/2024 to the FY function (in a class), you've probably already guessed.
I think that already in this procedure you should check that the data passed to the class is correct.
VBA Code:
Sub CEDOrder_Test()
    Dim ord As clsCEDOrder
    Dim dtDate As Date
    Dim intL As Integer

    On Error Resume Next
    dtDate = #4/12/2024#    'OR DateSerial(2024, 4, 12) OR "2024-4-12" OR "2024/4/12"; BUT NOT "4/12/2024" !!!
    intL = 183

    If Err.Number <> 0 Then
        MsgBox "Invalid data type", vbExclamation
        Exit Sub
    End If
    On Error GoTo 0

    Set ord = New clsCEDOrder

    ord.ProceedDate = dtDate
    ord.TDYLength = intL

    Debug.Print ord.CrossesFY
End Sub

Errors in the clsCEDOrder class.
Since you built in class properties, use them. Leave the variables declared at the module level only for Get and Let handling.
Below is the corrected code of the class. I specifically left the old commands commented out to make it easier to understand the changes.
VBA Code:
'CLASS MODULE - clsCEDOrder

'Member variables
Private m_FY As Integer
Private m_Length As Integer
Private m_CrossFY As Boolean
Private m_ProcDate As Date

'Properties
Property Get TDYLength() As Integer
    TDYLength = m_Length
End Property

Property Let TDYLength(ByVal intLen As Integer)
    m_Length = intLen
End Property


Property Get CrossesFY() As Boolean
    'FYCrossCheck
    'CrossesFY = m_CrossFY
    CrossesFY = FYCrossCheck
End Property

Private Property Let CrossesFY(ByVal blnXFY As Boolean)
    m_CrossFY = blnXFY
End Property


Property Get ProceedDate() As Date
    ProceedDate = m_ProcDate
End Property

Property Let ProceedDate(ByVal dtProc As Date)
    m_ProcDate = dtProc
End Property


'Methods
Private Function FYCrossCheck()
    'Sets m-CrossFY bool value if a fiscal year change occurs between start and stop dates.

    Dim intStartFY As Integer
    Dim intStopFY As Integer
   
    'If either proceed date or TDY length is blank or if the two values aren't recognized, set false and exit.
    'If m_ProcDate = 0 Or m_Length = 0 Or IsDate(m_ProcDate) = False Or IsNumeric(m_Length) = False Then
    If ProceedDate = 0 Or TDYLength = 0 Then
        'm_CrossFY = False
        FYCrossCheck = False
        Exit Function
    End If
   
    'ID the start and stop fiscal year values.
'    intStartFY = FY(DateValue(m_ProcDate))
'    intStopFY = FY(DateValue(m_ProcDate) + TDYLength)
    intStartFY = FY(ProceedDate)
    intStopFY = FY(ProceedDate + TDYLength)
   
    'Compare and let result.
'    If intStartFY <> intStopFY Then
'        m_CrossFY = True
'    Else
'        m_CrossFY = False
'    End If
    FYCrossCheck = (intStartFY <> intStopFY)
End Function


Private Function FY(Optional ByVal dt As Date) As Long
    'Returns the 4 character FY value of a given date.
   
    'Set default value if no date was given.
    If dt = 0 Then
        dt = Date
    End If
   
    'Set FY to the month of the provided date.
    Select Case Month(dt)
        Case 10 To 12
            FY = Year(dt) + 1
        Case Else
            FY = Year(dt)
    End Select
End Function
On line:
VBA Code:
FYCrossCheck = (intStartFY <> intStopFY)
the parentheses are redundant, but they make it easier to see the right and left sides of the assignment.

Artik
 
Upvote 0
Thanks for taking the time to write all that out, @Artik. I definitely had some eureka moments.

Since you built in class properties, use them. Leave the variables declared at the module level only for Get and Let handling.
Yeah, that makes a lot of sense. I think I was trying to incorporate the m_CrossFY variable to tie in the Let property, but not sure why I kept that theme with ProceedDate and TDYLength.

VBA Code:
Property Get CrossesFY() As Boolean
    'FYCrossCheck
    'CrossesFY = m_CrossFY
    CrossesFY = FYCrossCheck
End Property
I originally had FYCrossCheck as a function just like this, but was worried that (I think) it cuts the Let property out. Should I just delete the Let property for CrossesFY?

FYCrossCheck = (intStartFY <> intStopFY)
The scales have fallen from my eyes! I have other code that could benefit from that little change. I also agree with the parentheses use.

By passing the date as text you expose yourself to implicit type conversion.
I had thought of this issue before trying to learn classes and took some steps to validate entry on a textbox_exit event, but I didn't think about region format problems. I was planning to shift validation to the same class to validate the Property Let ProceedDate() As Date. Just haven't gotten to it, yet. Here's the pre-class process I had:
VBA Code:
Private Sub txtProcDate_Exit(ByVal Cancel As MSForms.ReturnBoolean)
    'Format the date value if not blank.
    If txtProcDate.Value <> "" Then
        txtProcDate.Value = DateFormat_US_Short(txtProcDate.Value)
    End If
End Sub

Function DateFormat_US_Short(ByVal strDate As String) As Date
    'Formats a date as "m/d/yyyy".
    
    Dim dt As Date
    
    'Convert Value into a number for date formatting.
    If IsDate(strDate) Then
        dt = strDate
    Else
        dt = 2      'Should return "1/1/1900".
    End If
    
    'Return the formatted date.
    DateFormat_US_Short = Format(dt, "m/d/yyyy")
End Function
 
Upvote 0
Should I just delete the Let property for CrossesFY?
With the previous answer I was in a bit of a hurry, so I did not think this class through well. That seems to be more correct this way:
VBA Code:
Sub CEDOrder_Test()
    Dim ord As clsCEDOrder
    Dim dtDate As Date
    Dim intL As Integer

    On Error Resume Next
    dtDate = #4/12/2024#    'OR DateSerial(2024, 4, 12) OR "2024-4-12" OR "2024/4/12"; BUT NOT "4/12/2024" !!!
    intL = 183

    If Err.Number <> 0 Then
        MsgBox "Invalid data type", vbExclamation
        Exit Sub
    End If
    On Error GoTo 0

    Set ord = New clsCEDOrder

    With ord
        .ProceedDate = dtDate
        .TDYLength = intL

        .CrossesFY = .FYCrossCheck

        Debug.Print .CrossesFY
    End With
    
End Sub
VBA Code:
'CLASS MODULE - clsCEDOrder

'Member variables
Private m_FY As Integer
Private m_Length As Integer
Private m_CrossFY As Boolean
Private m_ProcDate As Date

'Properties
Property Get TDYLength() As Integer
    TDYLength = m_Length
End Property

Property Let TDYLength(ByVal intLen As Integer)
    m_Length = intLen
End Property


Property Get CrossesFY() As Boolean
    CrossesFY = m_CrossFY
End Property

Property Let CrossesFY(ByVal blnXFY As Boolean)
    m_CrossFY = blnXFY
End Property


Property Get ProceedDate() As Date
    ProceedDate = m_ProcDate
End Property

Property Let ProceedDate(ByVal dtProc As Date)
    m_ProcDate = dtProc
End Property


'Methods
Function FYCrossCheck() As Boolean
    'Return True if a fiscal year change occurs between start and stop dates.

    Dim intStartFY As Integer
    Dim intStopFY As Integer
   
    'If either proceed date or TDY length is blank or if the two values aren't recognized, set false and exit.
    If ProceedDate = 0 Or TDYLength = 0 Then
        FYCrossCheck = False
        Exit Function
    End If
   
    'ID the start and stop fiscal year values.
    intStartFY = FY(ProceedDate)
    intStopFY = FY(ProceedDate + TDYLength)
   
    'Compare and let result.
    FYCrossCheck = (intStartFY <> intStopFY)
End Function


Private Function FY(Optional ByVal dt As Date) As Long
    'Returns the 4 character FY value of a given date.
   
    'Set default value if no date was given.
    If dt = 0 Then
        dt = Date
    End If
   
    'Set FY to the month of the provided date.
    Select Case Month(dt)
        Case 10 To 12
            FY = Year(dt) + 1
        Case Else
            FY = Year(dt)
    End Select
End Function
Note that Property Let CrossesFY and function FYCrossCheck have become public procedures.

Artik
 
Upvote 0

Forum statistics

Threads
1,223,879
Messages
6,175,148
Members
452,615
Latest member
bogeys2birdies

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