IClock interface

Developer
May 29, 2008 at 9:48 PM
A trick that I picked up long ago was to create an IClock interface and then use that for getting all my times.  I'm introducing it into Umbrella for the simple reason to make my timespan extensions deterministic.  Here are the 2 main interfaces/classes -

using System;

namespace nVentive.Umbrella.Clock
{
    /// <summary>
    /// Simple interface for a clock
    /// </summary>
    public interface IClock
    {
        /// <summary>
        /// the current local time
        /// </summary>
        DateTime Now { get; }

        /// <summary>
        /// The current UTC time
        /// </summary>
        DateTime UtcNow { get; }

        /// <summary>
        /// The current local date
        /// </summary>
        DateTime Today { get; }
    }
}


using System;

namespace nVentive.Umbrella.Clock
{
    public sealed class SystemClock : IClock
    {
        public static readonly IClock Instance = new SystemClock();

        public DateTime Now
        {
            get { return DateTime.Now; }
        }

        public DateTime UtcNow
        {
            get { return DateTime.UtcNow; }
        }

        public DateTime Today
        {
            get { return DateTime.Today; }
        }
    }
}

With this in place, I added a clock property to my IDateTimeCalendarExtensions interface.  Then in my TimeSpanCalendarExtensions, I do this...

        public static DateTime FromNow(this TimeSpan self)
        {
            return Extensions.Clock.Now.Add(self);
        }

instead of

        public static DateTime FromNow(this TimeSpan self)
        {
            return DateTime.Now.Add(self);
        }

My unit tests have changed to...

        [Fact]
        [FreezeClock(2008, 10, 31, 10, 00, 00, DateTimeKind.Local)]
        public void FromNow()
        {
            using(ThreadLocalServiceLocator.Push(new FreezingDateTimeExtensionsLocator()))
            {
                var future = 10.Days().FromNow();
                var now = TimeSpanCalendarExtensions.Extensions.Clock.Now;

                Assert.Equal(future, now.AddDays(10));
                Assert.Equal(DateTimeKind.Local, future.Kind);
            }
        }

This way I don't have to worry about small differences in milliseconds (which I get randomly depending on what my system is doing while running the tests).

In order to write the unit tests, I had to upgrade my xUnit.NET to the latest version (FreezeClock is in XUnitExt.dll not XUnit.dll).  All tests build and run with the newest xUnit.NET.  If no one objects, I'll check them in this afternoon.
Sep 4, 2008 at 10:48 AM
If I understand correctly I could begin using TimeSpanCalendarExtensions.Extensions.Clock.Now instead of DateTime.Now in my code base to ease unit testing. That's a neat trick, but "TimeSpanCalendarExtensions.Extensions.Clock.Now" strike me as a bit long and certainly less readable than DateTime.Now. Have I misinterpreted the scenario in which this should be used?

Thanks.
Developer
Sep 7, 2008 at 8:42 PM
Hey Lars,$0$0$0$0It is a bit wordy :)  I've got some code in my local branch that reduces it.  I've been so heads down with "paying bills" work, that I haven't had a chance to push the changes in.  I'll post here when I get it done.$0$0$0$0$0Justin$0
Sep 8, 2008 at 6:11 AM

Looking forward to it :)

Developer
Sep 8, 2008 at 11:23 PM
Edited Sep 8, 2008 at 11:24 PM
You can check out change set http://www.codeplex.com/umbrella/SourceControl/DownloadSourceCode.aspx?changeSetId=15631. 

From the commit notes - Binding extensions renamed to Curry. Action and Func combined into one set of extension methods. Clock namespace renamed to Calendar. Clock class uses ServiceLocator to find an IClock instance or uses SystemClock singleton. IDateTimeCalendarExtensions no longer use IClock interface. Use specific properties for Now, UtcNow, and Today. Default implementation uses Clock from Calendar namespace. This makes it easier to swap out just the clock interface.

To use it is pretty sane now...Clock.Now, Clock.UtcNow, Clock.Today


If you want to change the clock, check out the test code in TimeSpanCalendarExtensionsFixture.
Sep 9, 2008 at 6:56 AM
Thanks,

I'll check that out shortly :)