Time to Roll Your Own Type

Normally when you’re working with a framework or language you assume that the library it ships with will provide types for most of your basic needs, and this is what I’ve always thought about the .NET framework. When you’re working with a .NET language you have access to a very rich set of libraries, covering all sorts of different functionality, except time. “Time!?”, you exclaim as you recoil in confusion, “what about DateTime and TimeSpan?”. Yes, they can both be used to model time but what if you want to work with just a single, fixed point in time, say for instance 09:30 in 24 hour format? Yes again, they can both do that but you’re going to need to keep passing around the correct formatting string, and how many places are you going to end up with that scattered around your code?

It was at this point whilst pairing with a colleague we realised that there isn’t a simple Time type offered by .NET’s Base Class Libary that expresses a fixed point in time. We came across this when we were working on a project that was dealing with the concept of a shop’s opening and closing times which were expressed in 24 hour format. Whilst writing the tests for the logic to list a shop’s opening hours we began discussing the usage of TimeSpan and how it was being used to model the time when a shop opened and closed. I personally don’t like the idea of using a type thats purpose is to represent a length of time as something to represent a fixed point in time. The MSDN documentation for TimeSpan says the following:

A TimeSpan object represents a time interval (duration of time or elapsed time) that is measured as a positive or negative number of days, hours, minutes, seconds, and fractions of a second. The TimeSpan structure can also be used to represent the time of day, but only if the time is unrelated to a particular date. Otherwise, the DateTime or DateTimeOffset structure should be used instead.

Microsoft state that TimeSpan can be “used to represent the time of day, but only if the time is unrelated to a particular date”, but for me that doesn’t necessarily feel right. When I was reading back what we had written for the feature mentioned above we had something along the lines of:

shop.OpeningTime = new TimeSpan(9, 30, 0);

I think that reads a little funny, the point in time at which this shop opens is a length of time? Arguably you could say the shop is open for a length of time but that’s not what this particular model wants, it wants the fixed point in time 09:30 am. What to do then? There’s always DateTime, but from the fragment of documentation above Microsoft seem to suggest that DateTime should be used to represent the time of day only when it’s related to a particular date. In our case it wasn’t and we wanted to represent a time independant of the day.

It’s Written All over Your Face

Me and my colleague were discussing the issue and I suggested what I thought the best solution would be and that was to introduce a new type. Creating new types left, right and centre isn’t something I take lightly and I was in two minds whether to suggest it in the first place. My first point of contention was potentially violating the principle of least astonishment which tells us our designs should be consistent with accepted norms. Further to what I mentioned above I would reason that C# developers are used to working with both the DateTime and TimeSpan classes even if I don’t think they read well, and would introducing a new time type just confuse our future selves and people joining the project? My second thoughts were around the 4 rules of simple design which prescribe that simple code adheres to the following rules:

  1. Passes all the tests
  2. Expresses every idea that we need to express
  3. Says everything once and only once
  4. Has no superfluous parts

With regards to the rules I felt undecided about a 24 hour time type as it felt like rules 2 and 4 were at odds with each other. It felt like adding it would be more expressive but at the same time would it be a case of breaking away from what other developers are familiar with and potentially inadvertently introducing confusion? By the look on my colleague’s face I could see he was having similar thoughts, but with all that in mind we decided to give it a go and see what it looked like, we could always checkout an earlier commit.

Time Will Tell

In the end we implemented a class called TwentyFourHourTime, snappy right! It’s a straightforward enough class in that it’s either constructed with hours and minutes as two seperate integers or with a string in HH:MM format. To format the time it delegates to an internal instance of DateTime, although in hindsight I’m not sure whether having this presentational concern mixed in is the best idea. The following isn’t our production code but I’ve rewritten something that’s along the same lines here. Below you can see the difference between working with DateTime, TimeSpan and our own type TwentyFourHourTime:

shop.OpeningTime = new TimeSpan(9, 30, 0);
shop.OpeningTime = new TwentyFourHourTime(09, 30);

var timeNow = new TwentyFourHourTime.From(DateTime.Now);
shop.IsOpen = timeNow.IsBetween(openingTime, closingTime);

In reflection I like TwentyFourHourTime for the following reasons:

  • It’s more specific to the domain we’re modelling than the time types offered by the .NET framework
  • It’s clearer about what it is to the reader than TimeSpan, it’s a little more explicit
  • It’s API reads more like a regular English sentence
  • It’s API is leaner, it doesn’t come with all the other cruft that that DateTime and TimeSpan come with
  • It conforms to convention and implements ToString, Equals and overloads various equality operators
public class ShopShould
{
  [Fact]
  public void be_open_at_five_when_opening_times_are_nine_till_six()
  {
    var openingTime = new TwentyFourHourTime(09,00);
    var closingTime = new TwentyFourHourTime(18,00);
    var now = TwentyFourHourTime.From(new DateTime(2017, 4, 26, 17, 0, 0));
    var shop = new Shop(openingTime, closingTime);
    Assert.True(shop.IsOpen(now));
  }

  [Fact]
  public void be_closed_at_six_when_opening_times_nine_till_five()
  {
    var openingTime = new TwentyFourHourTime(09, 00);
    var closingTime = new TwentyFourHourTime(17, 00);
    var now = TwentyFourHourTime.From(new DateTime(2017, 4, 26, 18, 0, 0));
    var shop = new Shop(openingTime, closingTime);
    Assert.False(shop.IsOpen(now));
  }
}

public class Shop
{
  private readonly TwentyFourHourTime _openingTime;
  private readonly TwentyFourHourTime _closingTime;

  public Shop(TwentyFourHourTime openingTime, TwentyFourHourTime closingTime)
  {
    _openingTime = openingTime;
    _closingTime = closingTime;
  }

  public bool IsOpen(TwentyFourHourTime now)
    => now.IsBetween(_openingTime, _closingTime);
}

I’ve been flip-flopping between whether this was a good design choice or not but I think I’ve finally made my peace with it. Grady Booch said the following of clean code:

Clean code is simple and direct. Clean code reads like well-written prose. Clean code never obscures the designers’ intent but rather is full of crisp abstractions and straightforward lines of control. – Grady Booch

I really like this description of software development as it hints to the authoring aspect of it, and that much more of our time is spent reading code than actually writing it. With regards to writing your own type for something as trivial as time and semi-reinventing the wheel, I think there’s definitely a point where you think “is this a bit pointless”? But naming things is important, and you shouldn’t underestimate the cognitive tax that bad abstractions and naming may impose on the reader. It’s for these reasons why I feel slight trepidation when creating types like this, it’s the pitfall of something seeming like a good idea at one point in time and then it coming back to bite you in the ass in the future.

Having said that when I think about it in the context of writing the type as part of the story of what’s happening in the domain, I’d be more inclined to call it a TwentyFourHourTime in natural language rather than a DateTime or a TimeSpan. As time goes on I think I’m favoring anything that reads more like plain English and if that means making a custom time type then so be it. For now it’s time to stop thinking about time.

Share

Leave a Reply

Your email address will not be published. Required fields are marked *

Post comment