Working with dates is very common in software. The need to track dates and do calculation based on dates crosses nearly every domain. In fact, dates are such a common part of every aspect of our lives that dealing with them is second nature to most of us.

Unfortunately, even though we are very familiar with dates, dates are complicated. Unlike numbers or strings dates are full of complicated boundaries and rules. What happens at midnight? What happens at the end of the month? What happens on a leap year? Etc. In order to build a system that uses dates we need to be aware of what happens at all of these boundaries and how our system will handle them.

In addition, business rules that deal with dates can be complicated. They can say things like, “On the last Thursday of the month, unless that Thursday is a holiday in which case we move it to the prior Thursday, or if that Thursday falls during the blackout period in December in which case it moves to the Thursday after the blackout period ends.” Such rules are complicated enough for humans to deal with, but when you try to put them in code they can be a real headache.

Because of these issues and others, testing business logic that deals with dates is one of the harder areas to work with. There are a few rules of thumb that will help us:

Never use “Now” in a test

Often times business logic involving dates is concerned with how a particular rule applies to the current time, right now. The most obvious way to deal with that is to have the method that calculates the rule call the system and get the current time.

Unfortunately, if you do that the only way to test it is to have the test also know what now is. Then, you can perform the same calculation in the test and see if the result matches what the code produces. There are a couple of problems with this:

1) There is a lapse in time between when the test calculates “now” and when the code under test does the same thing. If that gap falls on a boundary the test could fail.

2) There might be multiple interesting permutations of the method you are testing. If you are tied to the current time then you can only test one permutation, and the one you are testing might only happen to be one of the interesting ones.

Test all interesting permutations

Testing all interesting permutations is a general rule that applies to all data types, but it is truly important for dates, because the number of interesting permutations may be high and what constitutes an interesting permutation may not be obvious or may not be the same from one calculation to the next.

Boundaries are particularly difficult and may be easy to miss. Did you consider what happens at the end of the day? At the end of the month? At the end of the year? On a leap year? Are you looking at ranges of dates? Does the order of dates matter? Do you need to worry about different timezones? Do you need to differentiate workdays, weekends, holidays, etc?

Pass in the date

In order to test a number of interesting dates you need to pass them into the calculation. Thus, the method that does the calculation should never get the date for itself – whether it is “now,” or from a database, or from user input, etc. You should obtain the date at a higher level and pass it in to your calculation.

This is an application of a principle know as Dependency Injection (aka Inversion of Control.) There are two ways to accomplish this. Either the class that calls your calculation obtains the date itself and passes it in, or the class that contains the calculation obtains the date from some collaborator that is passed to it. Let’s look at both of these:

public class FunWithDates {
   // now is passed in from the caller
   public Date calculateSomeInterestingThing(Date now) {
      ...
   }
}

or:

public class MoreFunWithDates {
   private DateCalculatorThingy dateCalc;

   public MoreFunWithDates(DateCalculatorThingy dateCalc) {
      this.dateCalc = dateCalc;
   }

   public Date calculateSomeInterestingThing() {
      Date now = this.dateCalc.now();
      ...
   }
}

The first approach is simpler, but the second approach defines an additional class for the responsibility of determining what date to supply. Encapsulating this responsibility may be useful for many problems that are more complex than simply knowing what the system thinks the time is here and now.

Write the Test First

Of course, in an Agile environment this advice is generally applicable as well. However, dates make this particularly poignant. As I already mentioned, dates are hard, and because we use them every day we are particularly familiar with how they are hard.

It is easier to think of the ways that dates are hard than it is to speculate on a solution that would satisfy all permutations. So, encode the most interesting and representative dates in your test, one at a time, and create the simplest implementation that will make each test pass, one at a time. What you will arrive at is a solution that handles all the interesting cases you can think of, is easy to test, and is clearly documented by the tests.