1

How would I get this query to get the monthly count data for the past 12 months? I don't want to hard code the range, I want to use the current date DateTime.Now and get all the data for the past 12 months from that. I am trying to avoid adding a calendar table to the database and do this just using LINQ.

Some months might not have any data but I still need a count of 0 for those.

For example. If my data contains

Date     Count
12/2/2013, 4
10/1/2014, 1
11/5/2014, 6

The results should be, using the current date of 11/9/2014

11/2013, 0
12/1013, 4
 1/2014, 0
 2/2014, 0
 3/2014, 0
 4/2014, 0
 5/2014, 0
 6/2014, 0
 7/2014, 0
 8/2014, 0
 9/2014, 0
10/2014, 1
11/2014, 6

I can't get it to work. I think it's how I'm using Range but I'm not sure.

TimeSpan ts = new TimeSpan(365, 0, 0, 0);
DateTime yearAgo = DateTime.Now.Subtract(ts);

var changesPerYearAndMonth =
    from year in Enumerable.Range(yearAgo.Year, 1)
    from month in Enumerable.Range(1, 12)
    let key = new { Year = year, Month = month }
    join revision in list on key
          equals new { revision.LocalTimeStamp.Year, 
                       revision.LocalTimeStamp.Month } into g
    select new { GroupCriteria = key, Count = g.Count() };

I have modified the answer from this this link as a starting point. Linq: group by year and month, and manage empty months

I just found this article that is the same question but unanswered Linq - group by datetime for previous 12 months - include empty months

Community
  • 1
  • 1
Heinrich
  • 1,711
  • 5
  • 28
  • 61
  • 1
    Is the `LocalTimeStamp.Year` property always equal to `2013` ? Is the `LocalTimeStamp.Month` property always in the range `[1, 12]` ? It looks like you wanted to have `Enumerable.Range(yearAgo.Year, 2)` ... ? – Matt Ko Nov 09 '14 at 21:22
  • No, it is the past year from the current date. – Heinrich Nov 09 '14 at 23:18
  • You could use `ToLookup` instead of `GroupBy`. – Aron Nov 10 '14 at 01:31

4 Answers4

5

To get the past twelve months, use

var now = DateTime.Now;
var months = Enumerable.Range(-12, 12)
    .Select(x => new { 
        year = now.AddMonths(x).Year, 
        month = now.AddMonths(x).Month });

To be safe you should first move 'now' to the start of the month to avoid any end-of-month effects with AddMonth.

var now = DateTime.Now;
now = now.Date.AddDays(1-now.Day);

Complete example:-

 var list = new [] {
            new { LocalTimeStamp = DateTime.Parse("12/2/2013"), count = 4},
            new { LocalTimeStamp = DateTime.Parse("10/1/2014"), count = 1 },
            new { LocalTimeStamp = DateTime.Parse("11/5/2014"), count = 6}
        };

        var now = DateTime.Now;
        now = now.Date.AddDays(1-now.Day);
        var months = Enumerable.Range(-12, 13)
            .Select(x => new { 
                year = now.AddMonths(x).Year, 
                month = now.AddMonths(x).Month });

        var changesPerYearAndMonth =
            months.GroupJoin(list, 
                m => new {month = m.month, year = m.year}, 
                revision => new { month = revision.LocalTimeStamp.Month,
                                  year = revision.LocalTimeStamp.Year},
                (p, g) => new {month = p.month, year = p.year, 
                               count = g.Sum(a => a.count)});


        foreach (var change in changesPerYearAndMonth)
        {
            Console.WriteLine(change.month + " " + change.year +" " + change.count);
        }
Ian Mercer
  • 38,490
  • 8
  • 97
  • 133
  • I am not clear how to use this with the other query results. Would I include it with `from` in the linq query? – Heinrich Nov 10 '14 at 01:08
  • -1 It isn't clear enough how to use this. Especially with Linq to EF or Linq to SQL, which would require a `.ToList()`. – Aron Nov 10 '14 at 01:31
3

You don't need a 3-way join, you just need to filter your data before grouping.

1) Query expression syntax

// since your list item type was not posted, anyway same access as your LocalTimeStamp property
list = new List<DateTime>();
DateTime aYearAgo = DateTime.Now.AddYears(-1);
var dateslastYear = from date in list
                 where date > aYearAgo
                 group date by new { date.Year, date.Month } into g
                 select new { GroupCriteria = g.Key, Count = g.Count() };

2) Chained

dateslastYear = list.Where (d=>d>aYearAgo)
                    .GroupBy (date=>new{date.Year, date.Month });

3) If you want grouping by year/month pairs, including records of not existent entries, and also omitting those pairs that are older than a year occurring with the joined Enumerable.Range call:

  var thisYearPairs = from m in Enumerable.Range(1, DateTime.Now.Month)
                      select new { Year = DateTime.Now.Year, Month = m };
  var lastYearPairs = from m in Enumerable.Range(DateTime.Now.Month, 12 - DateTime.Now.Month + 1)
                      select new { Year = DateTime.Now.Year - 1, Month = m };
  var ymOuter = from ym in thisYearPairs.Union(lastYearPairs)
                join l in list on new { ym.Year, ym.Month } equals new { l.Year, l.Month } into oj
                from p in oj.DefaultIfEmpty()
                select new { a = ym, b = p == null ? DateTime.MinValue : p };
  var ymGroup = from ym in ymOuter
                group ym by ym into g
                select new { GroupCriteria = g.Key.a, Count = g.Key.b == DateTime.MinValue ? 0 : g.Count() };
andrei.ciprian
  • 2,895
  • 1
  • 19
  • 29
  • The data might be empty for some months in the past year but I still need to it return a count of `0` for those. I think that's what `Range` does but I'm not sure. Does your answer address this? I'm sorry if that didn't come across in my title but that's an important part of this questions. That's what the "empty months" means and it's also part of the link I included. – Heinrich Nov 09 '14 at 23:15
1

You are taking the range for the 12 months of last year only but you actually want the last twelve months.

You can do this using a Enumerable.Range and the AddMonths method:

var changesPerYearAndMonth =
    from month in Enumerable.Range(0, 12)
    let key = new { Year = DateTime.Now.AddMonths(-month).Year, Month = DateTime.Now.AddMonths(-month).Month }
    join revision in list on key
            equals new
            {
                revision.LocalTimeStamp.Year,
                revision.LocalTimeStamp.Month
            } into g
    select new { GroupCriteria = key, Count = g.Count() };
petelids
  • 12,305
  • 3
  • 47
  • 57
0
public int YearDiff(DateTime a, DateTime b)
{
    return (int) Math.Floor((a.Year + a.Month / 100.0 + a.Day / 10000.0) - (b.Year + b.Month / 100.0 + b.Day / 10000.0));
}
Sach
  • 10,091
  • 8
  • 47
  • 84
Derek Liang
  • 1,142
  • 1
  • 15
  • 22