Thursday, 13 October 2011

Using DomainTime Instead of DateTime

In a previous post about favouring DateTime.UtcNow over DateTime.Now I mentioned that I don't use the DateTime struct directly in code to obtain the current date or time. Instead I use what I call DomainTime, a wrapper around the DateTime struct. It looks like this:
   public static class DomainTime
{
internal static DateTime OverrideForTesting = DateTime.MinValue;

public static DateTime UtcNow { get { return OverrideForTesting == DateTime.MinValue ? DateTime.UtcNow : OverrideForTesting.ToUniversalTime(); } }

public static DateTime Now { get { return OverrideForTesting == DateTime.MinValue ? DateTime.Now : OverrideForTesting; } }

public static DateTime Today { get { return OverrideForTesting == DateTime.MinValue ? DateTime.Today : OverrideForTesting.Date; } }

public static DateTime MaxValue { get { return DateTime.MaxValue; } }

public static DateTime MinValue { get { return DateTime.MinValue; } }

internal static void Reset()
{
OverrideForTesting = DateTime.MinValue;
}
}

The whole point of using this class is that it makes it easy to test other classes that have some kind of time dependency. To use a contrived example, imagine that you've got a class which will only do its work if the time is between 1am and 2am. It makes the decision (should I work or should I not?) by checking DateTime.Now.

How do you test this? You _could_ run your tests just before 1am, between 1am and 2am, and then again after 2am - but that's just stupid. You should be able to run your tests anywhere, any time, and as many times as you want.

The solution, here, is to make the class depend on DomainTime.Now instead. By doing so, you can
override the current time during testing by setting it with the OverrideForTesting property.

Note that this property is an internal property. Expose it to your test assemblies by using the
[assembly: InternalsVisibleTo("Your.Test.Assembly")]

directive in the AssemblyInfo class in the assembly where DomainTime resides.

Now you can test to your heart's content. It's just a matter of setting the appropriate DateTime for each test.

One final note: The DomainTime.Reset() method is there for your SetUp() or TearDown() methods so that you can avoid having the DateTime set by one test bleed over into another test.

Binding Views to Navigation Elements

Last night I did a bit of work on how I bind views to navigation items. I have tended to include information about 'active tabs' as part of the view model (which fits well with the idea of having one model per view) - but I didn't like the hierarchy of view models that emerged from it.

UPDATE: After a an anonymous comment on this post I have updated the implementation to use ViewData rather than TempData as the commenter rightly pointed out that, while the TempData implementation will work, TempData is for redirect.

What I ended up doing was sticking a piece of data in the ViewData dictionary and pulling it out in the view to determine which tab should be rendered as 'active'. I created some extension methods for ViewData to do this:

 
public static class ViewDataExtensions
{
public static void SetNavigation(this ViewDataDictionary viewData, T navElement)
{
var key = GetNavKey(navElement);
viewData.Add(key, navElement);
}

public static T GetNavElement(this ViewDataDictionary viewData)
{
var key = GetNavKey(typeof(T));
T t;
try
{
t = (T)viewData[key];
}
catch
{
t = default(T);
}

return t;
}

private static string GetNavKey(T navElement)
{
return GetNavKey(navElement.GetType());
}

private static string GetNavKey(Type t)
{
return "sitenav:" + t.Name;
}
}

Then I created an action filter which I can stick on a controller and/or an action. Notice the AttributeUsage which specifies the allowable targets and that the attribute can be applied more than once (this could be important if you've got more than one menu):

[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method, AllowMultiple = true)]
public class BindNavigation : ActionFilterAttribute
{
private readonly object _navElement;

public BindNavigation(object navElement)
{
_navElement = navElement;
}

public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Controller.ViewData.SetNavigation(_navElement);
base.OnActionExecuting(filterContext);
}
}

I apply this to a controller like this:
   
[BindNavigation(MainNavigation.Members)]
public class MembersController : FlyingFieldsBaseController
{
...
}

And an action like this:
 
[BindNavigation(ProfileNavigation.Wall)]
public ActionResult Index(string id)
{
var model = _getMemberProfileViewQuery.Invoke(Guid.Parse(id));
return View(model);
}

As you can tell I am using enumerations for my different "types" of navigation (main header nav, left
hand nav for profile pages, left hand nav for club management pages, etc). The thing is, though, you
can use anything you like - because TempData stores things as objects (you'll notice that my TempData extension methods use generics so you get type safety as well). You could, for example, store an object that holds the state for several layers of navigation if that's what you need.

In the views you just do this:
@{
var currentSection = ViewData.GetNavElement();
}

and use 'currentSection' however you please. I just use it to determine if I should set a CSS "selected" class on my navigation items.

Thoughts are welcome!

Wednesday, 5 October 2011

Working with dates

Lately I've been doing a good amount of work on a website feature that requires the output of a relative date and time. For example, a entry written on a user's wall (the website has social aspects) may be annoted with "written by John Smith about two hours ago." It is a relatively straight forward task to accomplish this - but I thought I'd write a few words about some pitfalls you may come across.

Even if you have only every written the most trivial of applications you are likely to have used the DateTime structure in .NET (if you've written .NET apps, that is). And you're very likely to have used DateTime.Now; Let's take a closer look at the DateTime structure. According to Microsoft's documentation a DateTime "Represents an instant in time, typically expressed as a date and time of day." And, DateTime.Now "Gets a DateTime object that is set to the current date and time on this computer, expressed as the local time."

Cool. DateTime.Now is really handy for getting a handle on the current time, and we've all used it. But now I'm going to tell you that you shouldn't.

DateTime.Now returns a DateTime object that represents "the current date and time on this computer, expressed as the local time." As convenient as this may be, it's a potential source of trouble. "This computer" is the computer where the code executes. It could be your desktop machine, a development server, a production server, or a mobile device. The time on that machine depends on where the machine is located (which timezone it is in) and whether or not the machine is affected by daylight savings. So why is this a problem?

The problem is that if you cannot guarantee that all the machines in your infrastructure are all in the same timezone and are equally affected (or not affected) by daylight savings, using DateTime.Now in your code will potentially yield different timestamps on different machines even if the call to DateTime.Now was made at exactly the same time on the machines in question. 1pm in Oslo, Norway, on October 5th 2011 is not the same time as 1pm in London, UK, on the same date. "But that's just silly", I hear you say. "All our infrastructure is in the same data centre in one place." OK. Fine. That may very well be the case. But what about your users. Where are they? Are all of them in the same time zone as your servers? And is it likely that you'll never grow beyond having only local users and having only one data centre?

Even if your answer to the above questions is "we'll never scale beyond one data centre and all our users are in the same place and always will be" I think you should keep reading. It might just make your life a little simpler down the road. Just in case.

The problem with DateTime.Now is that it always represents the local time of the machine on which the code executes, and you don't really want to worry about where that machine is, because doing so makes life as a developer painful. What you want to do instead is use DateTime.UtcNow which returns an instance of DateTime representing the Coordinated Universal Time (UTC) of now. UTC is the local time of the server less any timezone differences and less any daylight savings difference. If you only ever store and use UTC DateTimes then none of your DateTime comparisons will ever have to take into consideration any time differences caused by time zones or daylight savings.

The only thing you now have to worry about is the thing you should worry about, which is displaying the correct date to your end users. You'll have to adjust for the timezone they're in because a date for an event at 12pm in London should be rendered as 1pm for a user in Oslo (they're always an hour ahead of London time).

Regardless of whether you use UTC or not, you'll always have to consider time zones and daylight savings when rendering dates for a user. Using UTC, that's all you'll have to worry about. If you use DateTime.Now, however, you'll also have to ensure that you know what the time offset of that DateTime instance is if you're going to compare it to another date, or if you're going to render it to a user. Pain in the arse (PITA).

I reckon that you should always use UTC times in your applications regardless of what your user base might look like. It makes life simpler from the start, and if you ever need to support users across different timezones you'll be a step ahead.

So, as a rule, this is what I do:

  • Always call DateTime.UtcNow and never DateTime.Now. (In fact, I don't use DateTime... I use DomainTime which is a wrapper I've created around DateTime. I'll write more about that in a later post).
  • Always treat DateTime stored in a database as UTC. This means that when I read that DateTime out of the database I specify that it is a UTC DateTime using the DateTime.SpecifyKind() method. This is very important, because the machine will by default treat any DateTime as local time.

    Note that if you're using an ORM such as NHibernate you need to tell the ORM that the date should be treated as UTC. With FluentNHibernate this is really simple:

    Map(x => x.DateCreated).CustomType().Not.Nullable();

  • Lastly, before displaying the date to the user, I apply the time difference between UTC and the user's location. There are several ways of doing this. For example you can have your users tell you which time zone they're in and you can apply the offset. Or, if your users are web users you can use Javascript's Date.getTimezoneOffset() method and apply the difference (in minutes) to your UTC date. Check out this StackOverflow question for some specific pitfalls of that particular method.

I'll follow up on this post with another post or two about displaying relative times and how to use a DomainTime wrapper around your dates.