Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Find the Missing Months

Tags:

c#

datetime

I've got my own way of doing this but I'm not convinced its the best, in C#

Given a List<DateTime>, a DateTime startDate and an DateTime endDate. How would you return a new List<DateTime>for every month between startDate and endDate that is not included within the original List<DateTime> inclusive of the startDate and endDate.

Dates are not guarnteed to be the start of the month, could be any date within the month.

startDate and endDate could span multiple years.

The returned list should contain the first day of every month that is missing.

Thanks, and I hope it makes sense.

like image 611
mat-mcloughlin Avatar asked Jan 14 '11 12:01

mat-mcloughlin


3 Answers

var list = new List<DateTime>
    {
        new DateTime(1231223423433132),
        new DateTime(13223123132),
        new DateTime(12333123132),
        new DateTime(123345123132),
        DateTime.Now,
        new DateTime(5634534553)
    };

var allYearMonthes = list.Select(o => 
                             Eumerable.Range(1, 12)
                                 .Select(q => new { o.Year, Month = q }))
                          .SelectMany(o => o);

var enumerable = allYearMonthes.Except(list.Select(o => new { o.Year, o.Month }));

var dateTimes = enumerable.Select(o => new DateTime(o.Year, o.Month, 1));

EDIT: for those who interested in probably complete solution:

DateTime StartDate = DateTime.Now, EndDate = DateTime.Now.AddYears(5).AddMonths(2);
var allYearMonthes = Enumerable.Range(StartDate.Year, EndDate.Year - StartDate.Year -1)
                               .Select(o => Enumerable.Range(1, 12)
                               .Select(q => new { Year = o, Month = q }))
                               .SelectMany(o => o);

var enumerable = allYearMonthes.Except(list.Select(o => new { o.Year, o.Month }));
var dateTimes = enumerable.Select(o => new DateTime(o.Year, o.Month, 1));
like image 72
Jahan Zinedine Avatar answered Nov 20 '22 09:11

Jahan Zinedine


Well, assuming the same month in different years is considered different:

    private List<DateTime> GetUnincludedMonths(DateTime startDate, DateTime endDate,
                                               IEnumerable<DateTime> dates)
    {
        var allMonths = new HashSet<Tuple<int, int>>(); //month, year
        DateTime date = startDate;
        while (date <= endDate)
        {
            allMonths.Add(Tuple.Create(date.Month, date.Year));
            date = date.AddMonths(1);
        }
        allMonths.Add(Tuple.Create(endDate.Month, endDate.Year));

        allMonths.ExceptWith(dates.Select(dt => Tuple.Create(dt.Month, dt.Year)));
        return allMonths.Select(t => new DateTime(t.Item2, t.Item1, 1)).ToList();
    }
like image 39
Ohad Schneider Avatar answered Nov 20 '22 08:11

Ohad Schneider


Here's what I would do:

static IEnumerable<DateTime> GetMissingMonths(IEnumerable<DateTime> currentDates, DateTime startDate, DateTime endDate)
{
    var yearMonths = new HashSet<Tuple<int, int>>(currentDates.Select(d => Tuple.Create(d.Year, d.Month)));
    DateTime current = new DateTime(startDate.Year, startDate.Month, 1);
    if (current < startDate)
        current = current.AddMonths(1);
    while (current <= endDate)
    {
        if (!yearMonths.Contains(Tuple.Create(current.Year, current.Month)))
        {
            yield return current;
        }
        current = current.AddMonths(1);
    }
}

EDIT: if you can't use Tuple, you can use an anonymous type instead, with a helper method to create the HashSet:

static IEnumerable<DateTime> GetMissingMonths(IEnumerable<DateTime> currentDates, DateTime startDate, DateTime endDate)
{
    var yearMonths = MakeHashSet(currentDates.Select(d => new { d.Year, d.Month }));
    DateTime current = new DateTime(startDate.Year, startDate.Month, 1);
    if (current < startDate)
        current = current.AddMonths(1);
    while (current <= endDate)
    {
        if (!yearMonths.Contains(new { current.Year, current.Month }))
        {
            yield return current;
        }
        current = current.AddMonths(1);
    }
}

static HashSet<T> MakeHashSet<T>(IEnumerable<T> source)
{
    return new HashSet<T>(source);
}

The MakeHashSet method allows you to use type inference to create a HashSet<T> when T is an anonymous type.

like image 2
Thomas Levesque Avatar answered Nov 20 '22 09:11

Thomas Levesque