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