Search
Close this search box.

Always use Nullables for Dates: C# and VB.NET

Always use Nullables for dates at the least. Trust me on this. I dogged Nullables for the longest time because I thought they were buggy, then I realized today when testing that I was using them wrong.  Nullables allow you to actually have null values, which for dates is arguably a must.  Why? Keep reading…

Tip: Never call Nullable.Value. The nullable item will error out if it is a null value. This is where I was going wrong and thought they were buggy. Just call the nullable item and it will return the value if it exists.

C# 2.0

DateTime? returnDate = null;
DateTime? d = returnDate.Value;  // Will error
DateTime? d1 = returnDate;       // Will NOT error

VB2005

Dim returnDate As Nullable(Of DateTime) = Nothing Dim d As
    Nullable(Of DateTime) = returnDate.Value  'Will error  Dim d1 As
                            Nullable(Of DateTime) = returnDate 'Will NOT error

The second thing to keep in mind is that you can pass the type <T> into a Nullable<T> and it will automatically use it without having to cast.  You can also set a nullable item to another nullable of the same type without calling any functions or properties of the nullable.

C# 2.0

DateTime? d = new DateTime(1900,01,01);

VB2005

Dim d As System.Nullable(Of DateTime) = New DateTime(1900, 1, 1)

You can also check to see if they have a value and even get the value or a default.

C# 2.0

DateTime? testDate = null;
if (testDate.HasValue) {
}
testDate.GetValueOrDefault();
testDate.GetValueOrDefault(new DateTime(1753, 01, 01));

VB2005

Dim testDate As Nullable(Of DateTime) =
    Nothing If(testDate.HasValue) Then End If testDate
        .GetValueOrDefault() testDate.GetValueOrDefault(New DateTime(1753, 1,
                                                                     1))

Nullables Play Nice With Databases and NHibernate

NHibernate is nice in that if you have a null value, it will not save it to the database. 

If you have a value of anything NHibernate will attempt to save it.  If you are rolling your own, you have to implement checks based on a date for non nullables. Who wants all of that extra code? 

Why could date checking (or lack thereof) be a problem? What date does DateTime.MinValue give you? What does MS SQLServer require a date to be?  What about Oracle? That’s right kiddies, craptastic!  We’re suddenly in a world of hurt and we need a bunch of code to check all of our dates. But wait, that’s where Nullables come to the rescue!

Removing Dates That are Meant to be Null Values

So let’s settle on a date and if we are less than that, we want a null date. It’s actually scary simple to implement. Let’s settle on anything before 1900 is meant to be null. You  can choose any value you would like here. The naming convention of the helper function isn’t quite where I want, what if the date changes? ReSharper (R#) gives me the ability to change that whenever I want. 😀

C# 2.0

public class DateHelper {
  private static readonly DateTime FIRST_GOOD_DATE = new DateTime(1900, 01, 01);

  public static DateTime? MapDateLessThan1900ToNull(DateTime? inputDate) {
    DateTime? returnDate = null;

    if (inputDate >= FIRST_GOOD_DATE) {
      returnDate = inputDate;
    }

    return returnDate;
  }
}

And more importantly, the tests to verify the helper works appropriately (MbUnit):

[TestFixture]
public class As_A_DateHelper {
  [SetUp]
  public void I_want_to() {}

  [Test]
  public void
  Verify_MapDateLessThan1900ToNull_returns_null_date_when_passed_a_null_nullable_date() {
    DateTime? testDate = null;
    Assert.AreEqual(null, DateHelper.MapDateLessThan1900ToNull(testDate));
  }

  [Test]
  public void
  Verify_MapDateLessThan1900ToNull_returns_null_date_when_passed_null() {
    Assert.AreEqual(null, DateHelper.MapDateLessThan1900ToNull(null));
  }

  [Test]
  public void
  Verify_MapDateLessThan1900ToNull_returns_null_date_when_passed_Dec_31_1899() {
    DateTime testDate = new DateTime(1899, 12, 31);
    Assert.AreEqual(null, DateHelper.MapDateLessThan1900ToNull(testDate));
  }

  [Test]
  public void
  Verify_MapDateLessThan1900ToNull_returns_date_when_passed_Jan_01_1900() {
    DateTime testDate = new DateTime(1900, 01, 01);
    Assert.AreEqual(testDate, DateHelper.MapDateLessThan1900ToNull(testDate));
  }

  [Test]
  public void
  Verify_MapDateLessThan1900ToNull_returns_date_when_passed_normal_date() {
    DateTime testDate = new DateTime(2008, 08, 06);
    Assert.AreEqual(testDate, DateHelper.MapDateLessThan1900ToNull(testDate));
  }

  [Test]
  public void
  Verify_MapDateLessThan1900ToNull_returns_null_date_when_passed_nullable_Dec_31_1899() {
    DateTime? testDate = new DateTime(1899, 12, 31);
    Assert.AreEqual(null, DateHelper.MapDateLessThan1900ToNull(testDate));
  }

  [Test]
  public void
  Verify_MapDateLessThan1900ToNull_returns_date_when_passed_nullable_Jan_01_1900() {
    DateTime? testDate = new DateTime(1900, 01, 01);
    Assert.AreEqual(testDate, DateHelper.MapDateLessThan1900ToNull(testDate));
  }

  [Test]
  public void
  Verify_MapDateLessThan1900ToNull_returns_date_when_passed_normal_nullable_date() {
    DateTime? testDate = new DateTime(2008, 08, 06);
    Assert.AreEqual(testDate, DateHelper.MapDateLessThan1900ToNull(testDate));
  }
}

VB2005

Public Class DateHelper

    Private Shared ReadOnly FIRST_GOOD_DATE As New DateTime(1900, 1, 1)

        Public Shared Function
    MapDateLessThan1900ToNull(ByVal inputDate As System.Nullable(Of DateTime))
        As System.Nullable(Of DateTime) Dim
    returnDate As System.Nullable(Of DateTime) =
    Nothing

    If inputDate
    >= FIRST_GOOD_DATE Then
    returnDate = inputDate End If

                 Return returnDate End Function

                 End Class

                 <TestFixture()> _ Public Class As_A_DateHelper

                 <SetUp()> _ Public Sub I_want_to() End Sub

                 <Test()> _ Public Sub
                 Verify_MapDateLessThan1900ToNull_returns_null_date_when_passed_a_null_nullable_date()
                     Dim testDate As System.Nullable(Of DateTime) =
        Nothing Assert
            .AreEqual(Nothing,
                      DateHelper.MapDateLessThan1900ToNull(testDate)) End Sub

        <Test()> _ Public Sub
        Verify_MapDateLessThan1900ToNull_returns_null_date_when_passed_null()
            Assert
            .AreEqual(Nothing,
                      DateHelper.MapDateLessThan1900ToNull(Nothing)) End Sub

        <Test()> _ Public Sub
        Verify_MapDateLessThan1900ToNull_returns_null_date_when_passed_Dec_31_1899()
            Dim testDate As New DateTime(1899, 12, 31) Assert
            .AreEqual(Nothing,
                      DateHelper.MapDateLessThan1900ToNull(testDate)) End Sub

        <Test()> _ Public Sub
        Verify_MapDateLessThan1900ToNull_returns_date_when_passed_Jan_01_1900()
            Dim testDate As New DateTime(1900, 1, 1) Assert
            .AreEqual(testDate,
                      DateHelper.MapDateLessThan1900ToNull(testDate)) End Sub

        <Test()> _ Public Sub
        Verify_MapDateLessThan1900ToNull_returns_date_when_passed_normal_date()
            Dim testDate As New DateTime(2008, 8, 6) Assert
            .AreEqual(testDate,
                      DateHelper.MapDateLessThan1900ToNull(testDate)) End Sub

        <Test()> _ Public Sub
        Verify_MapDateLessThan1900ToNull_returns_null_date_when_passed_nullable_Dec_31_1899()
            Dim testDate As System.Nullable(Of DateTime) =
            New DateTime(1899, 12, 31) Assert
                .AreEqual(Nothing, DateHelper.MapDateLessThan1900ToNull(
                                       testDate)) End Sub

            <Test()> _ Public Sub
            Verify_MapDateLessThan1900ToNull_returns_date_when_passed_nullable_Jan_01_1900()
                Dim testDate As System.Nullable(Of DateTime) =
                New DateTime(1900, 1, 1) Assert
                    .AreEqual(testDate, DateHelper.MapDateLessThan1900ToNull(
                                            testDate)) End Sub

                <Test()> _ Public Sub
                Verify_MapDateLessThan1900ToNull_returns_date_when_passed_normal_nullable_date()
                    Dim testDate As System.Nullable(Of DateTime) =
                    New DateTime(2008, 8, 6) Assert.AreEqual(
                        testDate,
                        DateHelper.MapDateLessThan1900ToNull(testDate)) End Sub

                    End Class

That’s more test code than actual code.  Some might say it’s overkill, but not me. I know this crap works when I’m done. 😀 

Notice the naming conventions of the tests. If you are looking for a quick BDD solution, you can’t go wrong with using this or a similar naming convention.  With MbUnit, the execution report reads nice. 

As_A_DateHelper.I_want_to.Verify_MapDateLessThan1900ToNull_returns_null_date_when_passed_a_null_nullable_date

You know exactly what is tested without even having to look at the code. I have told you what I am testing, what I am giving it, and what I expect to get back.

Thoughts?

Print | posted @ Wednesday, August 06, 2008 9:47 PM

This article is part of the GWB Archives. Original Author: Fervent Coder

Related Posts