What is the C# Syntax for something like date.IsWithIn(x months).Of(Comparison Date)?

The title is a bit whacky, but that's the question. I'm using C#. I'm trying to come up with several DateTime extension methods. While thinking about it, I wondered what syntax it would take for me to write the code as follows:

DateTime comparisonDate = DateTime.Now.AddMonths(-3);

if( DateTime.Now.IsWithIn(3).Of(comparisonDate) ) ....

I've written extension methods before, but I'm not sure how composing something like this would work. The "IsWithIn" would be one method...but would this return an Expression and the "Of" method would be an extension method of the Expression class?

EDIT 1

Still thinking about this. I'm wondering if this approach, though readable, is over complicating things. My first revision just tweaks @Wai Ha Lee's is to the DateTimeExtensions class. I'll refactor this and keep iterating. This screams strategy pattern but I haven't fit that into it yet. There's also no "Of" method now and the method name seems to make sense...today at least. A month from now? I'm not sure.

I also thought of another way I'd like to be able to write this code. I guess I'm still dreaming, but here that goes:

date.IsWithIn(1).Days().Of(comparisonDate);
date.IsWithIn(1).Months().Of(comparisonDate);
date.IsWithIn(1).Years().Of(comparisonDate);

But that aside, here's the revision I have that's merely a DateTime extension without the method name chaining.

public class Program
{
    static void Main(string[] args)
    {
        DateTime now = DateTime.Now;
        DateTime past = new DateTime(2015, 1, 15);
        DateTime future = new DateTime(2015, 3, 15);
        DateTime comparison = now.AddDays(-2);
        int interval = 1;
        DateInterval di = DateInterval.Days;

        Console.WriteLine(
            string.Format("Now, {0}, is with in {1} {2} of {3} is {4}",
                now.ToShortDateString(),
                interval.ToString(),
                di.ToString(),
                comparison.ToShortDateString(),
                now.IsDateWithinXRangeOfAnotherDate(interval, di, comparison).ToString())
        );

        Console.ReadLine();
    }
}

  public enum DateInterval
    {
        Days,
        Months,
        Years
    }

public static class DateTimeExtensions
{
    public static bool IsDateWithinXRangeOfAnotherDate(this DateTime date, int interval, DateInterval dateInterval, DateTime comparisonDate)
    {
        DateTime _min = comparisonDate;
        DateTime _max = comparisonDate;

        switch(dateInterval)
        {
            case DateInterval.Days:
                _min = _min.AddDays(-interval);
                _max = _max.AddDays(interval); 
                Console.WriteLine(
                    string.Format("Min Date is {0} Max Date is {1}",
                        _min.ToShortDateString(),
                        _max.ToShortDateString()));
                break;
            case DateInterval.Months:
                _min = _min.AddMonths(-interval);
                _max = _max.AddMonths(interval);
                Console.WriteLine(
                    string.Format("Min Date is {0} Max Date is {1}",
                        _min.ToShortDateString(),
                        _max.ToShortDateString()));
                break;
            case DateInterval.Years:
                _min = _min.AddYears(-interval);
                _max = _max.AddYears(interval);
                Console.WriteLine(
                    string.Format("Min Date is {0} Max Date is {1}",
                        _min.ToShortDateString(),
                        _max.ToShortDateString()));
                break;
        }

        return _min <= date && date <= _max;
    }        
}

EDIT 2

Revising:

date.IsWithIn(1).Days().Of(comparisonDate);
date.IsWithIn(1).Months().Of(comparisonDate);
date.IsWithIn(1).Years().Of(comparisonDate);

to

date.IsWithIn(1.Days()).Of(comparisonDate);
date.IsWithIn(1.Months()).Of(comparisonDate);
date.IsWithIn(1.Years()).Of(comparisonDate);

After looking at FluentTime for a bit, I noticed the author used several methods and classes I didn't even know existed. For one, he used the TimeSpan.FromDays method. He may have overloaded the + symbol because, at another point in the code, he just adds the timespan to a date. Given how TimeSpans work, I may only be able to implement the 1.Days() portion...and I think that's all I really need.

Will keep playing around with all this until I get it figured out. I could just use the FluentTime library, but it's overkill for what I need this for as the library handles time as well. I'm strictly interested in date range comparisons. Methods like After(), Before(), IsBetween(), IsWithIn. I've implemented the first 3 already. This question pivots on answering the last one.

EDIT 3 - SOLVED!

This question has become more an exercise in code than practicality. Ultimately, Jon Skeet was right about having to create a custom type. The solution breaks down to this summary:

A custom class: FluentDateTime was created 3 int Extension methods - Days, Months, Years. These each return a FluentDateTime class. 1 DateTime extension method - IsWithIn that takes a FluentDateTime parameter

I want to emphasize that this is a lot of buck for very little bang...but, regardless, here's the code.

public class FluentDateTime
    {

        public enum DateInterval
        {
            Days,
            Months,
            Years
        }

        private DateTime _lowDate;
        private DateTime _highDate;
        public DateTime BaseDate { get; set; }
        public DateInterval Interval { get; set; }
        public int Increment { get; set; }


        public bool Of(DateTime dt)
        {
            _lowDate = dt;
            _highDate = dt;

            if(this.Interval == DateInterval.Days)
            {
                _lowDate = _lowDate.AddDays(-this.Increment);
                _highDate = _highDate.AddDays(this.Increment);
            }
            else if (this.Interval == DateInterval.Months)
            {
                _lowDate = _lowDate.AddMonths(-this.Increment);
                _highDate = _highDate.AddMonths(this.Increment);
            }
            else
            {
                _lowDate = _lowDate.AddYears(-this.Increment);
                _highDate = _highDate.AddYears(this.Increment);
            }

            Console.WriteLine(
                string.Format("{0} <= {1} <= {2}", _lowDate.ToShortDateString(), BaseDate.ToShortDateString(), _highDate.ToShortDateString()
                ));

            return (_lowDate < BaseDate && BaseDate < _highDate) || (_lowDate.Equals(BaseDate) || _highDate.Equals(BaseDate) );            
        }

    }

// DATETIME EXTENSION

public static FluentDateTime IsWithIn(this DateTime date, FluentDateTime fdtParams)
{
    fdtParams.BaseDate = date;
    return fdtParams;
}

//INT EXTENSIONS

 public static FluentDateTime Days(this int inc)
        {
            FluentDateTime fdt = new FluentDateTime();
            fdt.Interval = FluentDateTime.DateInterval.Days;
            fdt.Increment = inc;
            return fdt;
        }

        public static FluentDateTime Months(this int inc)
        {
            FluentDateTime fdt = new FluentDateTime();
            fdt.Interval = FluentDateTime.DateInterval.Months;
            fdt.Increment = inc;
            return fdt;
        }

        public static FluentDateTime Years(this int inc)
        {
            FluentDateTime fdt = new FluentDateTime();
            fdt.Interval = FluentDateTime.DateInterval.Years;
            fdt.Increment = inc;
            return fdt;
        }

//TEST PROGRAM

DateTime testDate1 = new DateTime(2015, 3, 3);
            DateTime testDate2 = new DateTime(2015, 3, 4);
            Console.WriteLine(
                string.Format("{0} is within 5 days of {1}? {2} (should be true)",
                    testDate1.ToShortDateString(), testDate2.ToShortDateString(), testDate1.IsWithIn(5.Days()).Of(testDate2)
                ));

            testDate1 = new DateTime(2015, 3, 1);
            testDate2 = new DateTime(2015, 3, 7);
            Console.WriteLine(
                string.Format("{0} is within 3 days of {1}? {2} (should be false)",
                    testDate1.ToShortDateString(), testDate2.ToShortDateString(), testDate1.IsWithIn(3.Days()).Of(testDate2)
                ));

            testDate1 = new DateTime(2015, 3, 3);
            testDate2 = new DateTime(2015, 4, 1);
            Console.WriteLine(
                 string.Format("{0} is within 1 month of {1}? {2} (should be true)",
                     testDate1.ToShortDateString(), testDate2.ToShortDateString(), testDate1.IsWithIn(1.Months()).Of(testDate2)
                 ));


            testDate1 = new DateTime(2015, 3, 3);
            testDate2 = new DateTime(2015, 6, 1);
            Console.WriteLine(
                string.Format("{0} is within 2 month of {1}? {2} (should be false)",
                    testDate1.ToShortDateString(), testDate2.ToShortDateString(), testDate1.IsWithIn(2.Months()).Of(testDate2)
                ));
Jon Skeet
people
quotationmark

IsWithin would have to return some sort of type representing a range of values, remembering the "centre" and the range size. Now Of could be an extension method of that, or could easily be a normal instance method, given that you'd be writing the type yourself.

Note that 3 isn't clear in terms of being 3 days, 3 hours or something else. You should work out how you want to specify that. You could take a TimeSpan instead of just an int, or have separate IsWithinDays, IsWithinHours etc methods.

people

See more on this question at Stackoverflow