Dru Sellers

Sponsors

The Lounge

Advertisement

Images in this post missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at imagehelp@codebetter.com
Kick the DateTime.Now addiction

How many times do we naively rely on DateTime.Now for business dates. Yikes, I just reviewed a system that I wrote a while back and I have chained myself to this concept in a very painful way. Now, I am not responsible for this system any longer but I wanted to try and think of a solution to this problem so that I don't do it again.

Captain Obvious says: "So with time we seem to have two major units that we care about 'Date' and 'Time'."

Dates are all about a calendar so lets have an ICalendar and times are all about clocks so lets have an IClock too. Now we can have some concrete SystemCalender and SystemClock that basically just wrap the DateTime.Now type functions. The more interesting behavior for your business would be / could be wrapped in a concrete implementation called, wait for it, Business Calendar and BusinessClock.

public interface IClock
{
	DateTime Now {get;}
}

public interface ICalendar
{
	DateTime Today {get;}
}

Some things that I like about this. I like that this can be database backed, or by a filesystem, or a configuration file. It really becomes less of a concern if I need to change something. If I care more about calendar than clocks I can always use the system clock but then use the business calendar. Really at this point its all about making the choice an explicit choice. Its another one of these small things that can make your system much easier to rerun should a run fail, or just more tolerant of changes in time or what holidays you recognize, etc.

public class SystemClock : IClock
{
	public DateTime Now { get { return DateTime.Now; } }
}

public class SystemCalendar : ICalendar
{
	public DateTime Today { get{ return DateTime.Today; } }
}

not to abuse extension methods (oh what the hell why not, its fun)

public static class CalendarExtensions
{
	public static bool IsHoliday(this ICalendar calendar)
	{
		return _holidayRepository.Find(calendar.Today).IsNotNull();
	}
}

Ok, the code above is way contrived and totally made up but I like how the base calendars don't have to have every function you may want and can instead focus on the problem of representing dates and times the correct way and that you can bolt on stuff for your app later. Of course you can get yourself into trouble this way as well but sharp tools are nice!

As always leave improvements in the comments!

-d


Posted Sun, Jan 11 2009 10:22 AM by drusellers

[Advertisement]

Comments

Jesper Kamstrup Linnet wrote re: Kick the DateTime.Now addiction
on Sun, Jan 11 2009 12:12 PM

I once worked on a project where a lot of the business logic was very dependent on dates. Using an interface like the ICalendar was a great help, especially when writing unit tests exercising the date related logic.

Damien Guard wrote re: Kick the DateTime.Now addiction
on Sun, Jan 11 2009 12:23 PM

This makes dealing with time zones more difficult.

[)amien

Roman Jendrusz wrote re: Kick the DateTime.Now addiction
on Sun, Jan 11 2009 12:57 PM

As Jesper said it is a really great way for writing  a more testable code. I think that many projects have some fancy helper methods for dealing with time and dates.  As you said these metods can be provided by this interfaces or extension methods.

What is your prefered way to gain access to objects that implements these interfaces? Are you using some service locator?

Paul wrote re: Kick the DateTime.Now addiction
on Sun, Jan 11 2009 1:02 PM

@Damien, it's not immediately obvious to me why this makes using time zones more difficult than calling DateTime.Now directly? Can you expand and suggest any improvements you would make?

Avner Kashtan wrote re: Kick the DateTime.Now addiction
on Sun, Jan 11 2009 1:39 PM

Your implementation is pretty simple and straightforward, but I'm just missing one thing - what's so horrible about using DateTime.Now? I can understand scenarios where the current server's system clock isn't the standardized time you want to rely on, but what is the rationale for marking DateTime.Now as a total no-no?

Chris wrote re: Kick the DateTime.Now addiction
on Sun, Jan 11 2009 1:58 PM

It would be easier to see why you would want to clutter a codebase with these interfaces if you could provide real examples of  the problem with DateTime.Now, followed by examples of how hiding behind the an interface is helpful and more testable.

Paul wrote re: Kick the DateTime.Now addiction
on Sun, Jan 11 2009 2:15 PM

@Chris, it could aid in testing as now you can create a mock IClock or ICalendar to return a deterministic date for testing purposes

DotNetKicks.com wrote Kick the DateTime.Now addiction
on Sun, Jan 11 2009 2:18 PM

You've been kicked (a good thing) - Trackback from DotNetKicks.com

Igor Brejc wrote re: Kick the DateTime.Now addiction
on Sun, Jan 11 2009 2:22 PM

I agree with abstracting Date/Time, but I think your interfaces should return DateTimeOffset instead of DateTime.

see www.danrigsby.com/.../datetime-vs-datetimeoffset-in-net

Chris wrote re: Kick the DateTime.Now addiction
on Sun, Jan 11 2009 2:40 PM

@Paul,

isn't this deterministic:

new DateTime(2008, 12, 25)

??

Paul wrote re: Kick the DateTime.Now addiction
on Sun, Jan 11 2009 2:51 PM

@Chris, yes but it isn't DateTime.Now. If the method under test uses DateTime.Now as part of it's internal logic, then how are you going to control that when you come to test it. You're not going to change the source code of your method are you?

Chris wrote re: Kick the DateTime.Now addiction
on Sun, Jan 11 2009 3:02 PM

Right, so back to my first comment, i just need an example so I can wrap my head around this.  

To date(no pun intended), this would solve exactly zero problems I've had, so rather than a theoretical example, I'm curious of an original problem that this solves.  And not one that was made up this morning to help support this post, just one from a real project.  Actually, the one that inspired Dru's post would be perfect.

Jimmy Bogard wrote re: Kick the DateTime.Now addiction
on Sun, Jan 11 2009 3:05 PM

We used to use the IClock quite a bit in our projects BUT....it got quite annoying having the IClock dependency littered everywhere.

We're now leaning towards the Ayende (tm) way of things:

www.lostechies.com/.../systemtime-versus-isystemclock-dependencies-revisited.aspx

Yeah, I'll be That Guy who links to his own blog.  What _is_ nice about wrapping the dependencies in an interface is the extension methods.

However, extension methods around primitives are often a sign that you have a case of primitive obsession.  Why not separate creation (SystemTime.Now or whatever) and behavior?  You could have a pretty rich SystemTime or SystemCalendar if you put all the behavior pertaining to those items actually in that class.

Paul wrote re: Kick the DateTime.Now addiction
on Sun, Jan 11 2009 3:10 PM

@Chris

Here is a real life example from a project Ayende was having problems testing.

ayende.com/.../Dealing-with-time-in-tests.aspx

Chris wrote re: Kick the DateTime.Now addiction
on Sun, Jan 11 2009 3:48 PM

@Paul,

I just can't buy into the whole abstracting DateTime.Now.

There are two ends of the spectrum of testing.  The ones who just flat out don't do it at all, and the ones who spend WAY too much time overthinking things like how to test DateTime.Now.  

Ayende's post actually indicates that there wasn't a problem with his non-test code, just his test code, so time was spent(wasted) trying to make his test work, while all that time his non-test code was working fine.

IClock, IDateTime, and ISystemTime, et al, can all very easily be replaced with another interface: ILikeToWasteMyClientsMoney

I'm wearing my flame retardant tshirt today :)

Jimmy Bogard wrote re: Kick the DateTime.Now addiction
on Sun, Jan 11 2009 3:58 PM

@Chris

His non-test code was working fine?  How did he prove that?  Gut feeling? Manual testing when we feel like it?

Tests/specifications are the only repeatable, reliable way to know if we built the software right.

Paul wrote re: Kick the DateTime.Now addiction
on Sun, Jan 11 2009 4:13 PM

@Chris,

Ha :-) It's ok you can hang it back up for another day.

I know what you are saying in that his production code was working but the question is how would you know that other than by checking it manually or via an automated test.

I've no vested interest in trying to persuade you. I'm by no means an expert (or zealot) in tdd. I've just been using more and more over the last few years. I think tdd is a very personal experience and it's only when you've experienced some real value from an extra level of decoupling than you may normally have chose on a few of your projects that it becomes easier to buy into more and more of these kind of pre-emptive decisions.

And please don't take any of that as implying that you don't get tdd or anything, pragmatism rules the day and if you say you don't need it this particular abstraction for your projects then I believe you. Who better to make that decision.

Chris wrote re: Kick the DateTime.Now addiction
on Sun, Jan 11 2009 4:14 PM

@Jimmy,

I would 100 percent agree with the your last statement so long as it was prefaced with one word,  "[Useful] Tests/specifications are the only repeatable, reliable way to know if we built the software right."

Hacking up an interface to test whether DateTime.Now works is no different than testing getters and setters, waste of time.

Paul wrote re: Kick the DateTime.Now addiction
on Sun, Jan 11 2009 4:29 PM

@Chris,

Also I don't think anyone is suggesting that the interface is to help test if DateTime.Now works. It is useful for helping to test if your user code responds appropriately when it has to use the current time as part of its logic, in a deterministic way.

Chris wrote re: Kick the DateTime.Now addiction
on Sun, Jan 11 2009 4:46 PM

@Paul,

I was kinda generalizing(slash mocking) with the DateTime.Now remarks.  

It's hard to not see wrapping DateTime.Now behind an interface as overkill.  I'm a huge TDD fan, this particular thing though, I just don't get it.  I'm sure next week(month) I'll hit my first time sensitive coding issue and the only way of testing it is with an ISomethingToDoWithDateAndTime interface.

Paul wrote re: Kick the DateTime.Now addiction
on Sun, Jan 11 2009 5:03 PM

@Chris,

If I had this conversation at work, I'm sure 90%+ of the guys I deal with would say all the things you've said. It's very hard for it not to sound like overkill until it's caused you some pain. For the record I don't implement this solution in every project I do, but I know it's there in the toolbox should I need it.

The thing is because it's such a trivial thing to implement you can see why people might just use it from the off if they think they might need it. The YAGNI discussion is for another day though.

Software Warlock » Avoiding DateTime.Now wrote Software Warlock » Avoiding DateTime.Now
on Sun, Jan 11 2009 5:20 PM

Pingback from  Software Warlock » Avoiding DateTime.Now

Andrew wrote re: Kick the DateTime.Now addiction
on Sun, Jan 11 2009 5:25 PM

I have implemented the abstracted clock for a new project and it is proving very useful. We use the Static Gateway Pattern from this website, although Ayende's way also looks like a neat alternative.

Our code regularly has to deal with time zones and handling them right is very important for the business but far from straightforward. Having the code properly unit tested around edge cases is a life saver, and certainly helped to speed up the development.

Jimmy Bogard wrote re: Kick the DateTime.Now addiction
on Sun, Jan 11 2009 6:12 PM

@Chris

We're not testing DateTime.Now, but rather, code that _uses_ DateTime.Now.  Any time we have code that looks at DateTime.Now for decisions, it will be a brittle test UNLESS we're able to specify that indirect input for that test.

All it really takes to shift to this kind of abstraction is one test/build failure because of an environmental concern.  When we had tests fail because of what time zone our build server was on, we moved to an abstraction.  Going forward, our tests were easier to discern when both the observed "today" date as well as calculated dates were specified.

drusellers wrote re: Kick the DateTime.Now addiction
on Sun, Jan 11 2009 6:24 PM

@Roman: As a big IoC guy I would probably request either the calendar or the clock via constructor injection.

@Avner: see below. I wouldn't say that DateTime.Now is a total no-no, but rather you should make an active and aware decision to use it, and not just let it be the default.

@Chris: point taken.

@Igor: See DateTimeOffset, even better

@Jimmy: Ohhh, that's nice too.

Where I work we run processes 24 hours a day, sometimes things have to run after midnight for stuff that has technically happened the day before. Before this abstraction we had to, HAD TO, make sure we had the processes complete before midnight. If we didn't, it would screw up the calculation of someone's loan (if memory serves we basically dropped a day's interest). We could always solve it with workarounds but it was painful and messy. Time is an important aspect in our domain as well, which is another reason why the abstraction has come up.

Wow! You guys rock and I can't wait for more. Thanks for all the comments, I hope my response has cleared some of this up.

-d

Dew Drop - January 12, 2009 | Alvin Ashcraft's Morning Dew wrote Dew Drop - January 12, 2009 | Alvin Ashcraft's Morning Dew
on Mon, Jan 12 2009 11:23 AM

Pingback from  Dew Drop - January 12, 2009 | Alvin Ashcraft's Morning Dew

Andrew wrote re: Kick the DateTime.Now addiction
on Mon, Jan 12 2009 11:32 AM

>>Where I work we run processes 24 hours a day, sometimes things have to run after midnight for stuff that has technically happened the day before. Before this abstraction we had to, HAD TO, make sure we had the processes complete before midnight. If we didn't, it would screw up the calculation of someone's loan (if memory serves we basically dropped a day's interest).

There's your problem, it was just bad design.  In this situation you never should have been using DateTime.Now in the first place.  You always should have been passing in the date (or dates if necessary).

I'm with Chris on this one, you've overcomplicated a system which (more the likely) could have been solved with one or two DateTime parameters.

Robz wrote re: Kick the DateTime.Now addiction
on Mon, Jan 12 2009 10:12 PM

@Andrew: In Dru's defense, he didn't write the system that does this crazy stuff.  

We are keeping our fingers crossed that that system can be rewritten. It's a very bad egg.

igorbrejc.net » Fresh Catch For January 13th wrote igorbrejc.net » Fresh Catch For January 13th
on Tue, Jan 13 2009 8:03 AM

Pingback from  igorbrejc.net » Fresh Catch For January 13th

Andrew wrote re: Kick the DateTime.Now addiction
on Tue, Jan 13 2009 12:21 PM

@Robz

I probably should have worded it a bit differently.  I have quite  a bit of experience working in the banking industry, and there is some truly fugly code out there based around the fact that most banks do nightly processing.  Unfortuately for them, banking is now pretty much 24/7 (Credit Card processors generally have downtime over the weekends though), so I've dealt with all the same pains in the past.

Granted, I know next to nothing about the system in question otther than that processing can extend past midnight (a very common issue in the finance industry), I just can't see how messing around with DateTime.Now can solve the problem since the code that drives the Date Time should never have been there in the first place.

There should be a basic thought process that takes effect when tackling a problem: 1) Why does something need to be done? 2) What do we need to do? and 3) How do we do it?  The problem is, a lot of developers think in reverse.  We come up with nifty, cool solutions to problems that don't really need fixing.  We're worried about "the How" when we should be worried about "the What".   In this case, something as simple as DateTime.Now is now being turned into two interfaces, two classes and some extension methods.  Backing up a step further, someone needs to ask "Why" this problem needs to be fixed, and the answer is "because sometimes processing goes past midnight and DateTime.Now is no longer 'correct'".  So basically, we have "bad" data, so instead of overcomplicating the bad data to insure you get the right data, wouldn't it be easier for everyone to pass in the correct data in the first place (from a database, from a file, from a user control where they chose a Date, etc/)?  

DateTime.Now is great for timestamping a Forum Post, setting the value of a "Last Updated", etc. but it shouldn't be used to drive business logic if there is a possiblity that it might prevent it from working.

Also, if this is indeed just a temporary fix becase the project is so bad, why not JFHCI with a fix, throw a "HACK" comment above it and move on since that logic has a very, very good chance of being ripped out of that particular method anyways?

Just playing Devil's advocate more than anything, I have a few years of banking experience and I'm just shocked at how horrible the code is especially when it comes to their ETLs/Nightly Processing.  Makes me want to hide money under my mattress. ;)    

Karsten wrote re: Kick the DateTime.Now addiction
on Mon, Jan 19 2009 8:46 AM

What do you think about TypeMock?

I guess you will say that's a bad idea. But why?

It's a honest question. I'm very new to unit testing and still thinking about starting with TDD. But to me Typemock would seem as a good solution for this problem??

Discofunk wrote re: Kick the DateTime.Now addiction
on Thu, Feb 5 2009 12:56 AM

In a recent project we littered our code with DateTime.Now which caused a lot of headaches in the end.  We found it became important to make the distinction between a day and a time.  A clock object with the ability to freeze time, warp-into-the-future and blast-into-the-past is also helpful for unit testing timed events.

Add a Comment

(required)  
(optional)
(required)  
Remember Me?