|
By: Paul S Cilwa |
Posted: 4/10/2006 |
|
Page Views: 23 |
| Hashtags: #Namtira #VisualBasic #VBNET #ClassLibrary #NamtiraLib #SuperDates |
| Extension methods and classes to further enhance the date and time management classes. |
| Estimated reading time: 3 minute(s) (704 words) |
| Class AbsoluteMonth |
| Member | Parameters | Example |
| New | aDate As Date | Dim am As New AbsoluteMonth(Today) |
| New | aMonth As Int16, aYear As Int32 | Dim am As New AbsoluteMonth(3, 2026) |
| New | Months As Int32 | Dim am As New AbsoluteMonth(24319) |
| Value | — | am.Value → 24319 |
| ToDate | — | am.ToDate() → first of the month |
| AddMonths | Quantity As Int32 | am.AddMonths(6) |
| SubtractMonths | Quantity As Int32 | am.SubtractMonths(3) |
| + operator | Quantity As Int32 | am + 6 |
| Module SuperDates |
| Months | (extends TimeSpan) | myTimeSpan.Months |
| Months | EndDate As Date (extends Date) | startDate.Months(endDate) |
| IsNothing | (extends Date) | myDate.IsNothing → True if MinValue |
| ToDate | aDate | ToDate(someValue) → safe conversion |
The AbsoluteMonth Class
When you need to do arithmetic on months—say, figuring out how many
months apart two dates are, or adding a number of months to a date—working
with separate Year and Month properties is awkward. The AbsoluteMonth
class collapses a year and month into a single integer (year × 12 + month),
making month arithmetic as simple as adding and subtracting integers.
Imports System.Runtime.CompilerServices
Public Class AbsoluteMonth
Private myValue As Int32
Public Sub New(aDate As Date)
myValue = (aDate.Year * 12) + aDate.Month
End Sub
Public Sub New(aMonth As Int16, aYear As Int32)
myValue = (aYear * 12) + aMonth
End Sub
Public Sub New(Months As Int32)
myValue = Months
End Sub
End Class
Three constructors cover the common ways you might want to create an
AbsoluteMonth: from an existing Date, from a specific
month and year, or from a raw month count.
The Value property exposes the underlying integer, and
ToDate converts it back to a Date (set to the first
of the month).
Public ReadOnly Property Value As Int32
Get
Return myValue
End Get
End Property
Public Function ToDate() As Date
Return New Date(Value / 12, Value Mod 12, 1)
End Function
For month arithmetic, AddMonths and SubtractMonths modify
the value in place, and the + operator lets you write expressions
like anAbsoluteMonth + 6.
Public Sub AddMonths(Quantity As Int32)
myValue += Quantity
End Sub
Public Sub SubtractMonths(Quantity As Int32)
myValue -= Quantity
End Sub
Public Shared Operator +(ByVal anAbsoluteMonth As AbsoluteMonth,
ByVal Quantity As Int32) As AbsoluteMonth
anAbsoluteMonth.AddMonths(Quantity)
Return anAbsoluteMonth
End Operator
Counting Months
How many months are in a TimeSpan? .NET doesn't tell you, because
months vary in length. Our Months extension does a reasonable
approximation, accounting for leap years and rounding partial months.
Public Module SuperDates
<Extension()>
Public Function Months(ByVal Item As TimeSpan) As Int32
Dim Days As Int32 = Item.Days
Dim Years As Int32 = 0
Dim LeapYearCounter As Int16 = 0
Dim Result As Int32 = 0
While Days > 365
Years += 1
Days -= 365
LeapYearCounter += 1
If LeapYearCounter = 4 Then
LeapYearCounter = 0
Days -= 1
End If
End While
Result += Years * 12
While Days >= 28
Result += 1
Days -= 28
End While
If Days > 15 Then
Result += 1
End If
Return Math.Max(Result - 1, 1)
End Function
End Module
A second overload takes two Date values directly and returns
the number of months between them, again rounding partial months when
the day difference exceeds 15.
<Extension()>
Public Function Months(ByVal StartDate As Date, ByVal EndDate As Date) As Int32
Dim M As Int32 = EndDate.Month - StartDate.Month
Dim Y As Int32 = EndDate.Year - StartDate.Year
Dim D As Int32 = EndDate.Day - StartDate.Day
If M < 0 Then
Y -= 1
M = 12 + M
End If
If D > 15 Then
M += 1
End If
Return (Y * 12) + M
End Function
Fail-safe Date Handling
The Date structure is a value type and can never actually be
Nothing. By convention, we treat Date.MinValue as the
equivalent of a null date. The IsNothing extension makes this
check readable.
<Extension()>
Public Function IsNothing(aDate As Date) As Boolean
Return aDate = Date.MinValue
End Function
Similarly, ToDate safely converts any value to a Date.
If the value isn't a valid date, it returns Date.MinValue rather
than throwing an exception.
Public Function ToDate(aDate) As Date
If IsDate(aDate) Then
Return aDate
Else
Return Date.MinValue
End If
End Function