Find the Missing Months
I've got my own way of doing this but I'm not convinced its the best, in C#
Given a List<DateTime&g开发者_Python百科t;
, 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.
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));
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();
}
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.
LINQPad-working solution:
void Main()
{
var dates = new List<DateTime>
{
new DateTime(2011, 1, 1),
new DateTime(2011, 3, 5),
new DateTime(2011, 7, 28),
};
var startDate = new DateTime(2011, 1, 1);
var endDate = new DateTime(2012, 12, 31);
var existingMonths =
(from dt in dates
select dt.Year * 12 + dt.Month - 1).Distinct().ToArray();
var missingMonths =
from ym in Enumerable.Range(
startDate.Year * 12 + startDate.Month - 1,
(endDate.Year * 12 + endDate.Month) - (startDate.Year * 12 + startDate.Month) + 1)
where !existingMonths.Contains(ym)
select new DateTime(ym / 12, ym % 12 + 1, 1);
missingMonths.Dump();
}
public IEnumerable<DateTime> GetMissingMonths(
DateTime startDate,
DateTime endDate,
IEnumerable<DateTime> source)
{
IEnumerable<DateTime> sourceMonths =
source.Select(x => new DateTime(x.Year, x.Month, 1))
.ToList()
.Distinct();
return MonthsBetweenInclusive(startDate, endDate).Except(sourceMonths);
}
public IEnumerable<DateTime> MonthsBetweenInclusive(
DateTime startDate,
DateTime endDate)
{
DateTime currentMonth = new DateTime(startDate.Year, startDate.Month, 1);
DateTime endMonth = new DateTime(endDate.Year, endDate.Month, 1);
while(currentMonth <= endMonth)
{
yield return currentMonth;
currentMonth = currentMonth.AddMonths(1);
}
}
static void Main(string[] args)
{
var days = (new string[] { "3/23/2000", "7/3/2004", "1/3/2004", "3/1/2011" })
.Select(a => Convert.ToDateTime(a));
days = days.Select(a => a.AddDays(1 - (a.Day))).Distinct();
days = days.OrderBy(a => a);
var missingMonths = GetMissingMonths(days).ToList();
}
private static IEnumerable<DateTime> GetMissingMonths(IEnumerable<DateTime> days)
{
DateTime previous = days.First();
foreach (var current in days.Skip(1))
{
int months = (current.Month - previous.Month) +
12 * (current.Year - previous.Year);
for (int i = 1; i < months; i++)
{
yield return previous.AddMonths(i);
}
previous = current;
}
}
Thanks to Jani +1 for his idea. This one is one line of code :)
void Main()
{
var list = new List<DateTime>
{
new DateTime(2005, 10, 11),
new DateTime(2009, 3, 4),
new DateTime(2010, 5, 8),
new DateTime(2010, 8, 10),
DateTime.Now,
new DateTime(2010, 4, 8)
};
var result= Enumerable.Range(list.Min (l => l.Year), list.Max (l => l.Year) - list.Min (l => l.Year)).
SelectMany (e => Enumerable.Range(1, 12).Select (en => new DateTime(e, en, 1))).
Except(list.Select(o => new DateTime(o.Year, o.Month, 1))).
Where (o => o.Date > list.Min (l => l.Date) && o.Date < list.Max (l => new DateTime(l.Year, l.Month, 1)));
}
I'll throw my hat in, because it's fun. and I didn't see anyone putting a DateTime in a regular for loop, which I never get to do, so again... fun.
IEnumerable<DateTime> FindMissingMonths(DateTime startDate, DateTime endDate, IEnumerable<DateTime> inputs)
{
var allMonths = new List<DateTime>();
for (DateTime d = startDate; d < endDate; d = d.AddMonths(1))
{
allMonths.Add(new DateTime(d.Year, d.Month, 1));
}
var usedMonths = (from d in inputs
select new DateTime(d.Year, d.Month, 1)).Distinct();
return allMonths.Except(usedMonths);
}
fixed a bug, tested, works.
public IList<DateTime> GetMissingMonths(IList<DateTime> currentList, DateTime startDate, DateTime endDate)
{
// Create a list for the missing months
IList<DateTime> missingList = new List<DateTime>();
// Select a startdate
DateTime testingDate = startDate;
// Begin by the month of startDate and ends with the month of endDate
// month of startDate and endDate included
while(testingDate <= endDate)
{
if (currentList.Count(m => m.Month == testingDate.Month && m.Year == testingDate.Year) == 0)
{
missingList.Add(new DateTime(testingDate.Year, testingDate.Month, 1));
}
testingDate = testingDate.AddMonths(1);
}
return missingList;
}
精彩评论