1
\$\begingroup\$

I am trying to create a function similar to Excel's EOMONTH function in C#. I have written the following, however, I am not entirely sure if the it achieves the equivalent functionality. Is my equivalent of Excel's "Eomonth" function correct and are my tests sufficient?

public static DateTime EOMonth(this DateTime dateTime, int months = 0)
{
    DateTime firstDayOfTheMonth = new DateTime(dateTime.Year, dateTime.Month, 1);
    return firstDayOfTheMonth.AddMonths(1 + months).AddDays(-1);
}

Tests

[Test]
public void EOMonth_For5jan2011WithNoAddedMonths_ReturnsLastDayOfJan() 
{
    var expectedDate = DateTime.Parse("31-Jan-2011");
    var currentDate = DateTime.Parse("5-Jan-2011");

    var result = currentDate.EOMonth();

    Assert.That(result, Is.EqualTo(expectedDate));
}

[Test]
public void EOMonth_For5jan2011With_1_AddedMonths_ReturnsLastDayOfFeb2011()
{
    var expectedDate = DateTime.Parse("28-Feb-2011");
    var currentDate = DateTime.Parse("5-Jan-2011");

    var result = currentDate.EOMonth(1);

    Assert.That(result, Is.EqualTo(expectedDate));
}

[Test]
public void EOMonth_For5jan2011WithNegative_1_AddedMonths_ReturnsLastDayOfFeb()
{
    var expectedDate = DateTime.Parse("31-Dec-2010");
    var currentDate = DateTime.Parse("5-Jan-2011");

    var result = currentDate.EOMonth(-1);

    Assert.That(result, Is.EqualTo(expectedDate));
}

 [Test]
 public void EOMonth_For28Feb2007_12_AddedMonths_ReturnsLastDayOfFeb2008()
 {
     var expectedDate = DateTime.Parse("29-Feb-2008");
     var currentDate = DateTime.Parse("28-Feb-2007");

     var result = currentDate.EOMonth(12);

     Assert.That(result, Is.EqualTo(expectedDate));
 }
\$\endgroup\$
0

3 Answers 3

1
\$\begingroup\$

For your implementation, why not use:

return ((new DateTime(dateTime.Year, dateTime.Month, 1)).AddMonths(1 + months).AddDays(-1));

To me, that's a little cleaner, but it might just be me. When I'm just creating an object to be returned, I'm not a fan of storing it in a variable. Instead, just return exactly what you mean to and be done with it.


For your test cases:

I'm not sure that it matters in this instance, since you are relying exclusively on built in methods, but I would create test cases around February and March for the various types of leap years. What if I specify February 29th on a non-leap year? Does it work for the different leap year conditions? I'd also create test cases around new years (December and January).

\$\endgroup\$
1
\$\begingroup\$

Some general observations:

  • Your tests are locale dependent, DateTime.Parse("31-Jan-2011") will not work on every locale. I'd use new DateTime(2011, 01, 31) instead, but you could also specify a specific culture when parsing.
  • You never test the case where the input has a time part.
  • Your code won't work for December 9999, but I assume you can live with that.
  • The result always has Kind = DateTimeKind.Unspecified regardless of the Kind of the input. You should document your guarantees regarding Kind and perhaps add a test.

An important design question is if you want to provide functionality similar to EOMONTH but following C# and .NET conventions, or if you want it to be identical to Excel.

  • If you want to be identical to Excel, keep it like it is, but put it in a class and/or namespace that contains the word Excel.

  • If you want to adapt it to C#, you should rename it to EndOfMonth. I'd also drop the month parameter overload in that case, in favour of the caller using x.AddMonths(month).EndOfMonth() since I consider adding months and finding the end of the month different concerns that shouldn't be mixed within a function.

\$\endgroup\$
0
\$\begingroup\$

Thomas Owens has covered most points, but I would like to add that you should use varwhen you are declaring a local variable and the right-hand side of the declaration makes the type obvious, so:

DateTime firstDayOfTheMonth = new DateTime(dateTime.Year, dateTime.Month, 1);

should be:

var firstDayOfTheMonth = new DateTime(dateTime.Year, dateTime.Month, 1);

The reason to do this is that if you decide to change the type of the variable later, you only have to do it in one place, and it simplifies the code a little for a new reader.

You actually do this in your tests, which is good, but highlights the importance of consistency.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.