A Million Little Pieces Of My Mind

NamtiraLib

SuperDates

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)

.NET provides a powerful Date structure and a TimeSpan structure for working with dates and times. However, some common operations are either missing or unnecessarily verbose. SuperDates fills in those gaps with an AbsoluteMonth class for month-based arithmetic, and a handful of extension methods for everyday date tasks.

Class AbsoluteMonth
MemberParametersExample
NewaDate As DateDim am As New AbsoluteMonth(Today)
NewaMonth As Int16, aYear As Int32Dim am As New AbsoluteMonth(3, 2026)
NewMonths As Int32Dim am As New AbsoluteMonth(24319)
Valueam.Value24319
ToDateam.ToDate() → first of the month
AddMonthsQuantity As Int32am.AddMonths(6)
SubtractMonthsQuantity As Int32am.SubtractMonths(3)
+ operatorQuantity As Int32am + 6
Module SuperDates
Months(extends TimeSpan)myTimeSpan.Months
MonthsEndDate As Date (extends Date)startDate.Months(endDate)
IsNothing(extends Date)myDate.IsNothingTrue if MinValue
ToDateaDateToDate(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