C4SC DSL Series Catalog

 

Welcome to the first of several posts in the development of a DSL for working with DateTime in .NET. I’ll keep the intro light as there are a lot of things to cover. I just wanted to mention one thing though before we get started… Some of the class names you’ll see below are extremely wordy, but by the name alone they convey the full intent of the class internals. The wordy class names are only for the static methods containing the DSL’s extension methods. We’ll never have a need to instantiate these classes so you won’t see them in any code that uses it.

 

Looking Ahead to the Desired DSL Syntax…

The following code snippets are before-and-after examples of how to calculate some sample dates using the traditional approach available in the .NET Framework followed by the desired syntax I hope to achieve using the DateTime DSL we’re beginning in this post. You’ll notice here that the DSL doesn’t exactly save you many keystrokes, but it adds a great deal to the code’s readability and immediate understandability. I can see immediate usages for this DSL in providing a more readable unit test syntax for calculating and evaluating DateTime objects. I can also think of several times I wish I had something like this for manipulating DateTime in domain objects.

//Traditional .NET Framework...
DateTime tomorrow            = DateTime.Today.AddDays(1);
DateTime yesterday           = DateTime.Today.Subtract(new TimeSpan(-1));
DateTime oneYearFromNow      = DateTime.Today.AddYears(1);
DateTime silverAnniversary   = new DateTime(2012, 10, 20).AddYears(25);

 

//DSL Syntax...
DateTime tommorow             = 1.Day().FromNow();
DateTime yesterday            = 1.Day().Ago();
DateTime oneYearFromNow       = 1.Year().FromNow();
DateTime silverAnniversary    = 25.Years().From(user.weddingDate);

 

Step 1: The DSL Semantic Model

Martin Fowler defines a semantic model as “The model that’s populated by a DSL.” For the purposes of this post, that doesn’t tell us much… Without asking you to buy the book for further reading, I’ll provide a little more detail before we get to the code. In version 3.5 of the .NET Framework, Microsoft introduced extension methods allowing developers to extend class behavior by providing an additional set of methods (via a static class) without having to extend the original class implementation. This was an extremely power feature because it allowed .NET developers to extend classes in closed libraries or purchased 3rd party libraries where no source code was available. This approach is a similar technique to using modules in Ruby mixins, but it’s more restrictive since extension methods are constrained to a single Type. Adding extension methods for base classes and interface Types allows for more flexibility, but it’s still not as forgiving as what a truly dynamic language can provide.

 

In DSLs (a term used lightly here) that only use extension methods, the semantic model is whatever Type the extension methods are targeted for. In the case of this DSL as seen in the examples above, the desired syntax uses extension methods defined for the Int32 Type and not the final result Type of DateTime. Because a conversion between the two is required, we need an intermediate semantic model that can aggregate the components of DateTime before the actual DateTime instance is created. For that purpose, I’ve created an immutable struct called DateTimeComponents that contains a read-only field for all of the components of DateTime in the .NET Framework: years, months, days, minutes, seconds and milliseconds.

/// <summary>
/// Immutable semantic model for C4SC DateTime DSL.
/// </summary>
/// <remarks>
/// http://martinfowler.com/dslCatalog/semanticModel.html
/// </remarks>
public struct DateTimeComponents
{
    public DateTimeComponents(Int32 years, Int32 months, Int32 days, 
        Int32 minutes, Int32 seconds, Int32 milliseconds)
    {
        _years        = years;
        _months       = months;
        _days         = days;
        _minutes      = minutes;
        _seconds      = seconds;
        _milliseconds = milliseconds;
    }

    private readonly Int32 _years;
    public Int32 Years { get { return _years; } }

    private readonly Int32 _months;
    public Int32 Months { get { return _months; } }

    private readonly Int32 _days;
    public Int32 Days { get { return _days; } }

    private readonly Int32 _minutes;
    public Int32 Minutes { get { return _minutes; } }

    private readonly Int32 _seconds;
    public Int32 Seconds { get { return _seconds; } }

    private readonly Int32 _milliseconds;
    public Int32 Milliseconds { get { return _milliseconds; } }
}

 

Step 2: The Int32 Extension Methods

The DateTimeComponents struct is our semantic model that essentially collects the components of DateTime we will eventually convert to a DateTime object. As you’ll see from the abbreviated Int32ConversionToDateTimeComponents static class below, the extension methods here are extremely trivial in their functionality. They simply call the DateTimeComponents constructor passing in the various component of DateTime into the necessary constructor parameter and return the DateTimeComponents instance as the result. You’ll also notice that the singular version of each component method simply delegates control to its plural counter part. The abbreviated example below only shows the extension methods for collecting the year component of DateTime (although all other components are implemented). To see the full class implementation, visit my C4SC GitHub repository.

/// <summary>
/// Extension methods to convert Int32 values to their DateTimeComponents equivalent.
/// </summary>
public static class Int32ConversionToDateTimeComponents
{
    /// <summary>
    /// Converts an Int32 to its equivalent DateTimeComponents representation in years. 
    /// </summary>
    /// <param name="year">Number of years.</param>
    /// <returns><see cref="DateTimeComponents"/> composed of the given year parameter.</returns>
    public static DateTimeComponents Year(this Int32 year)
    {
        return year.Years();
    }

    /// <summary>
    /// Converts an Int32 to its equivalent DateTimeComponents representation in years. 
    /// </summary>
    /// <param name="years">Number of years.</param>
    /// <returns><see cref="DateTimeComponents"/> composed of the given years parameter.</returns>
    public static DateTimeComponents Years(this Int32 years)
    {
        return new DateTimeComponents(years, 0, 0, 0, 0, 0);
    }
}

 

Step 3: The DateTimeComponents Extension Methods

Below is the complete implementation of the DateTimeComponentsConversionToDateTime static class that contains all of the extension methods to return our desired DateTime. As you can see, these methods simply use the individual components of DateTime and either add or subtract them from the given DateTime. There are some slight optimizations we can perform here, but I’m going to save those for a future post. As I’ve mentioned before, C4SC is an agile shop and we’ll worry about refactoring and optimizing when there is sufficient unit test coverage to allow for it. If this class doesn’t make much sense yet, it will in the next section when I include some unit tests that reveal the DSL’s full intent.

/// <summary>
/// Extension methods to calculate DateTime future or past result from DateTimeComponents operand.
/// </summary>
public static class DateTimeComponentsConversionToDateTime
{
    /// <summary>
    /// Calculates the result of DateTimeComponents added to DateTime.Now().
    /// </summary>
    /// <param name="components"><see cref="DateTimeComponents"/> operand.</param>
    /// <returns>Future DateTime calculated from DateTime.Now().</returns>
    public static DateTime FromNow(this DateTimeComponents components)
    {
        return components.From(DateTime.Now);
    }

    /// <summary>
    /// Calculates the result of DateTimeComponents added to a given DateTime.
    /// </summary>
    /// <param name="components"><see cref="DateTimeComponents"/> operand.</param>
    /// <param name="dateTime">DateTime operand from which to calculate a future dateTime.</param>
    /// <returns>Future DateTime calculated from the given DateTimeComponents.</returns>
    public static DateTime From(this DateTimeComponents components, DateTime dateTime)
    {
        return dateTime.AddYears(components.Years)
            .AddMonths(components.Months)
            .AddDays(components.Days)
            .AddMinutes(components.Minutes)
            .AddSeconds(components.Seconds)
            .AddMilliseconds(components.Milliseconds);
    }

    /// <summary>
    /// Calculates the result of DateTimeComponents subtracted from DateTime.Now().
    /// </summary>
    /// <param name="components"><see cref="DateTimeComponents"/> operand.</param>
    /// <returns>Past DateTime calculated from DateTime.Now().</returns>
    public static DateTime Ago(this DateTimeComponents components)
    {
        return components.Ago(DateTime.Now);
    }

    /// <summary>
    /// Calculates the result of DateTimeComponents subtracted from a given DateTime.
    /// </summary>
    /// <param name="components"><see cref="DateTimeComponents"/> operand.</param>
    /// <param name="dateTime">DateTime operand from which to calculate a past date.</param>
    /// <returns>Past DateTime calculated from the given DateTimeComponents.</returns>
    public static DateTime Ago(this DateTimeComponents components, DateTime dateTime)
    {
        return dateTime.AddYears(-components.Years)
            .AddMonths(-components.Months)
            .AddDays(-components.Days)
            .AddMinutes(-components.Minutes)
            .AddSeconds(-components.Seconds)
            .AddMilliseconds(-components.Milliseconds);
    }
}

 

The Unit Tests

I’ve only included a couple of unit tests so you can get an idea of what we’ll actually be testing and how we’ll be testing it. I hope you’ll notice some immediate refactorings required by the tests below… We have a lot of duplicated test code across the tests and there are actually some fundamental issues with testing the results when working with the DateTime.Now value that cause the tests to fail intermittently. The intermittent failures occur mostly when testing the changes in milliseconds since those units of time are so small. For now though, they’re okay as we’ll be doing some refactoring work in the next post.

[Test]
public void it_should_add_1_year_to_now()
{
    DateTime actual      = 1.Year().FromNow();
    DateTime expected    = DateTime.Now.AddYears(1);

    Assert.That(actual.Year, Is.EqualTo(expected.Year));
    Assert.That(actual.Month, Is.EqualTo(expected.Month));
    Assert.That(actual.Day, Is.EqualTo(expected.Day));
    Assert.That(actual.Minute, Is.EqualTo(expected.Minute));
    Assert.That(actual.Second, Is.AtLeast(expected.Second));
    Assert.That(actual.Millisecond, Is.AtLeast(expected.Millisecond));
}

[Test]
public void it_should_subtract_1_year_from_now()
{
    DateTime actual     = 1.Year().Ago();
    DateTime expected   = DateTime.Now.AddYears(-1);

    Assert.That(actual.Year, Is.EqualTo(expected.Year));
    Assert.That(actual.Month, Is.EqualTo(expected.Month));
    Assert.That(actual.Day, Is.EqualTo(expected.Day));
    Assert.That(actual.Minute, Is.EqualTo(expected.Minute));
    Assert.That(actual.Second, Is.AtLeast(expected.Second));
    Assert.That(actual.Millisecond, Is.AtLeast(expected.Millisecond));
}

 

Resources