I have a form which user select start and end date to get data.
Although user select start and end date i have to show data from table week by week in date range.
My model is simple
public class DateBetween
public Datetime StartDate{ get;set;}
public Datetime EndDate{ get;set;}
I get list of my datas between these dates from database
IList<Revenue> datas = DB.CreateCrateria(typeof(Revenue))
.Add(Restrictions.Bt("Date", model.startDate, model.endDate))
.List<Revenue>();
public class Revenue
public int Id{ get;set;}
public double Revenue { get;set;}
public Datetime RevenueDate{ get;set;}
Example:
id Date Revenue
1 10/11/2011 554
2 11/10/2011 500
etc
If user select date like 6/30/2011 and 10/15/2011
I want to show to user
Week Date Avg.Revenue
Week 1 6/30/2011-7/2/2011 587
Week 2 7/3/2011-7/9/2011 650
...
etc
Is there any recommendation doing with aggregate funct. in linq
You could use a handwritten LINQ or use this answer:
LINQ query to split an ordered list into sublists of contiguous points by some criteria
Example: using nothing else than plain Linq and DateTime/Calendar:
var rnd = new Random();
var data = Enumerable.Range(1,100).Select(i => DateTime.Now.AddDays(rnd.Next(-91000,91000)/24));
var calendar = CultureInfo.CurrentCulture.Calendar;
Func<DateTime, int> twoWeeks = dt => (dt.Year * 100) + 2 * (calendar.GetWeekOfYear(dt, CalendarWeekRule.FirstFullWeek, DayOfWeek.Sunday) / 2);
var by2weeks = data.GroupBy(twoWeeks);
foreach (var period in by2weeks.OrderBy(g => g.Key))
{
Console.WriteLine("{0}: {1}", period.Key, string.Join(", ", period));
}
For C# 3.5 and earlier string.Join(", ", period)
string.Join(", ", period.Select(o => o.ToString()).ToArray())
The trick was getting the exact calendar start date for each week in any given year, and for that I looped on DateTime's Office Automation method. Doing that produced a Dictionary with 53 entries. After that, it was all standard LINQ grouping and referencing into the dictionary for the start date.
Calendar cal = Calendar.ReadOnly(CultureInfo.CurrentCulture.Calendar);
StringBuilder sb = new StringBuilder();
DirectoryInfo rdi = new DirectoryInfo(Root); // get all files in the root directory
List<FileInfo> allfis = rdi.GetFiles("*", SearchOption.AllDirectories).ToList();
var a = allfis.GroupBy(q=>cal.GetYear(q.LastWriteTime));
foreach (var b in a.Where(q=>q.Key==2011)) // this year only
{
double yearStartOaDate = new DateTime(b.Key, 1, 1).ToOADate();
double yearEndOaDate = yearStartOaDate + 365;
// get exact start dates for each week
Dictionary<int, DateTime> weekStartingDates = new Dictionary<int, DateTime>();
while (yearStartOaDate <= yearEndOaDate)
{
DateTime dt = DateTime.FromOADate(yearStartOaDate);
int ww = cal.GetWeekOfYear(dt, CalendarWeekRule.FirstDay, DayOfWeek.Sunday);
if(!weekStartingDates.ContainsKey(ww))
{
weekStartingDates.Add(ww, dt);
}
yearStartOaDate += ww == 1 ? 1 : 7;
}
var c = b.GroupBy(q => cal.GetWeekOfYear(q.LastWriteTime, CalendarWeekRule.FirstDay, DayOfWeek.Sunday)).OrderBy(q=>q.Key);
foreach(var d in c)
{
sb.AppendLine("Between " + weekStartingDates[d.Key].ToShortDateString() + " and " + weekStartingDates[d.Key].AddDays(6).ToShortDateString() + " there were " + d.Count() + " files modified");
}
}
File.WriteAllText("results.txt", sb.ToString());
And the results were...
Between 09/01/2011 and 15/01/2011 there were 22 files modified
Between 12/06/2011 and 18/06/2011 there were 11 files modified
etc etc.
First Select date object with WeekOfYear then group on it and ...
var result = datas.Select(p => new
{
week = EntityFunctions.DiffDays(EntityFunctions.CreateDateTime(p.Date.Year, 1, 1, 0, 0, 0), p.Date.Value).Value / 7,
Date= p.Date,
Revenue= p.Revenue
}).GroupBy(p => p.week)
.Select(p => new
{
week=p.Key,
Date=string.Format("{0:M/d/yyyy}",p.Min(q=>q.Date))+"-"+string.Format("{0:M/d/yyyy}",p.Max(q=>q.Date))
Revenue=p.Average(q=>q.Revenue)
}).ToList();
Related
I'm trying to create a function that returns the number of Dates in a Date Range that are sequential, starting on a specific date.
Example:
StartDate: 9/1/2022
Date Range: 9/1/2022, 9/2/2022, 9/3/2022, 9/4/2022, 9/7/2022
In this scenario the function I'm looking for would return 4.
Assume dates could be unordered and they can roll over into the next month, so with StartDate 9/29/2022:
9/29/2022, 9/30/2022, 10/1/2022, 10/4/2022 would return 3.
I know I can loop through the dates starting at the specific date and check the number of consecutive days, but I'm wondering if there's a clean way to do it with Linq.
This is the cleanest solution I can come up with...
var startDate = new DateTime(2022, 9, 1);
var days = new List<DateTime>()
{
new(2022, 8, 28),
new(2022, 9, 1),
new(2022, 9, 2),
new(2022, 9, 3),
new(2022, 9, 4),
new(2022, 9, 7)
};
var consecutiveDays = GetConsecutiveDays(startDate, days);
foreach (var day in consecutiveDays)
{
Console.WriteLine(day);
}
Console.ReadKey();
static IEnumerable<DateTime> GetConsecutiveDays(DateTime startDate, IEnumerable<DateTime> days)
{
var wantedDate = startDate;
foreach (var day in days.Where(d => d >= startDate).OrderBy(d => d))
{
if (day == wantedDate)
{
yield return day;
wantedDate = wantedDate.AddDays(1);
}
else
{
yield break;
}
}
}
Output is:
01.09.2022 0:00:00
02.09.2022 0:00:00
03.09.2022 0:00:00
04.09.2022 0:00:00
If you wanted the count, you can call .Count() on the result or just modify the method... Should be easy.
To count the number of consecutive dates in a given date range.
first parse the dates from a string and order them in ascending order.
Then, use the TakeWhile method to take a sequence of consecutive dates from the start of the list.
Finally, count the number of elements in the returned sequence and display the result.
public class Program
{
private static void Main(string[] args)
{
var dateRange = "9/29/2022, 9/30/2022, 10/1/2022, 10/4/2022";
var dates = dateRange
.Split(", ")
.Select(dateStr =>
{
var dateData = dateStr.Split("/");
var month = int.Parse(dateData[0]);
var day = int.Parse(dateData[1]);
var year = int.Parse(dateData[2]);
return new DateTime(year, month, day);
})
.OrderBy(x => x)
.ToList();
var consecutiveDatesCounter = dates
.TakeWhile((date, i) => i == 0 || dates[i - 1].AddDays(1) == date)
.Count();
Console.WriteLine(consecutiveDatesCounter);
}
}
Output: 3
Demo: https://dotnetfiddle.net/tYdWvz
Using a loop would probably be the cleanest way to go. I would use something like the following:
List<DateTime> GetConsecutiveDates(IEnumerable<DateTime> range, DateTime startDate)
{
var orderedRange = range.OrderBy(d => d).ToList();
int startDateIndex = orderedRange.IndexOf(startDate);
if (startDateIndex == -1) return null;
var consecutiveDates = new List<DateTime> { orderedRange[startDateIndex] };
for (int i = startDateIndex + 1; i < orderedRange.Count; i++)
{
if (orderedRange[i] != orderedRange[i - 1].AddDays(1)) break;
consecutiveDates.Add(orderedRange[i]);
}
return consecutiveDates;
}
Yet another approach using a loop. (I agree with the others that said a loop would be cleaner than using Linq for this task.)
public static int NumConsecutiveDays(IEnumerable<DateTime> dates)
{
var previous = DateTime.MinValue;
var oneDay = TimeSpan.FromDays(1);
int result = 0;
foreach (var current in dates.OrderBy(d => d))
{
if (current.Date - previous.Date == oneDay)
++result;
previous = current;
}
return result > 0 ? result + 1 : 0; // Need to add 1 to result if it is not zero.
}
var dates = new List<DateTime>() {new DateTime(2014,1,1), new DateTime(2014, 1, 2), new DateTime(2014, 1, 3) , new DateTime(2014, 1, 5), new DateTime(2014, 1, 6), new DateTime(2014, 1, 8) };
var startDate = new DateTime(2014,1,2);
var EarliestContiguousDates = dates.Where(x => x>=startDate).OrderBy(x => x)
.Select((x, i) => new { date = x, RangeStartDate = x.AddDays(-i) })
.TakeWhile(x => x.RangeStartDate == dates.Where(y => y >= startDate).Min()).Count();
You're either going to sort the dates and find sequential ones, or leave it unsorted and repeatedly iterate over the set looking for a match.
Here's the latter dumb approach, leaving it unsorted and using repeated calls to 'IndexOf`:
public static int CountConsecutiveDays(DateTime startingFrom, List<DateTime> data)
{
int count = 0;
int index = data.IndexOf(startingFrom);
while(index != -1) {
count++;
startingFrom = startingFrom.AddDays(1);
index = data.IndexOf(startingFrom);
}
return count;
}
Here im fetching record By using datetime.But i want that day Whole informatio Like Sum
Example
ID Name Date Amount
1 Samsung 1/1/2016 3000
2 LG 1/1/2016 6000
3 Videocon 2/2/2015 200
4 Philips 2/2/2015 1500
5 Sony 3/5/2015 15000
If I Select date Like 1/1/2016 its should Give me complete Result along with total --> 9000 & also its mentioned as Samsung-->3000 Lg--->6000 Total 9000
Here I have Some Code
public JsonResult Dif(int Date=0, int Month=0, int Year=0, int HH = 0, int MM = 0, int Ss = 0){
DateTime ss = new DateTime(Year, Month, Date, HH, MM, Ss);
var x = (from n in db.Employees where (n.DataofJoin == ss) select n);
return new JsonResult {Data=x, JsonRequestBehavior = JsonRequestBehavior.AllowGet };}
Try this:
var total = (from n in db.Employees where (n.DataofJoin == ss) select n.Amount).Sum();
Edit (thanks Mrinal for correction):
you may try following syntax:
//Get total
var total = employees.Where(ef => ef.DataofJoin == yourDate).Select(f => f.Amount).Sum();
//get list with total
var result = employees.Where(e => ef.DataofJoin == yourDate).ToList();
//Add object for total amount
result.Add(new employee { name = "Total", Amount = total });
var total = db.Employees.Where(x=>x.Date == yourDate).Select(y=> y.Amount).Sum();
If you want to receive the total for the Phone model per date.
var result = db.Employees.Where(x=>x.Date == yourDate).
GroupBy(x=>x.Name, y=> y.Amount,
(x, y) => new { PhoneName = x, TotalAmount = y.Sum()).ToList();
This will return you List of Phone per specific date and total amount about it.
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
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);
}
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() };
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() };
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));
}
My result in the MessageBox is lTest concatenated by strings. If I have dupilicate Keys, how can I group them by Key(Values)? For instance if Monday appears four times and Tuesday once, instead of Monday, Monday, Monday, Monday, Tuesday. I want it to appear Monday(4), Tuesday(1).
List<int> lNetworkIds = new List<int>();
Dictionary<DisplayDay, int> numDayOccurances = new Dictionary<DisplayDay, int>();
StringBuilder sb = new StringBuilder();
// get a list of distinct network id's for this proposal
foreach (Proposal lDetail in this._Proposal.Details)
{
if (!lNetworkIds.Contains(lDetail.NetworkId))
lNetworkIds.Add(lDetail.NetworkId);
if (!numDayOccurances.ContainsKey(lDetail.Daypart))
numDayOccurances[lDetail.Daypart] = 0;
numDayOccurances[lDetail.Daypart]++;
}
if (numDayOccurances.Count > 0)
{
string lTest = String.Join(", ", numDayOccurances.Keys);
MessageBox.Show(lTest);
}
It's not really clear from your question what you want to do, but if you have a list of days like so:
var days = new List<DayOfWeek> { DayOfWeek.Monday, DayOfWeek.Monday, DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday };
var result = from d in days
group d by d into g
select new
{
g.Key,
Count = g.Count()
};
Results in:
Key Count
Monday 3
Tuesday 1
Wednesday 1
You can then write this to a string as you see fit, for example:
String.Join(Environment.NewLine, result.Select(a => String.Format("{0} ({1})", a.Key, a.Count)))
Gives:
Monday (3)
Tuesday (1)
Wednesday (1)
I think you are looking for something like:
MessageBox.Show(
string.Join(",",
numDayOccurances.Keys.GroupBy(r=> r.DayOfWeek)
.Select(grp => string.Format("{0}({1})", grp.Key, grp.Count())));
Considering your class DisplayDay looks like:
class DisplayDay
{
public string DayOfWeek { get; set; }
}
I have a collection of dates stored in my object. This is sample data. In real time, the dates will come from a service call and I will have no idea what dates and how many will be returned:
var ListHeader = new List<ListHeaderData>
{
new ListHeaderData
{
EntryDate = new DateTime(2013, 8, 26)
},
new ListHeaderData
{
EntryDate = new DateTime(2013, 9, 11)
},
new ListHeaderData
{
EntryDate = new DateTime(2013, 1, 1)
},
new ListHeaderData
{
EntryDate = new DateTime(2013, 9, 15)
},
new ListHeaderData
{
EntryDate = new DateTime(2013, 9, 17)
},
new ListHeaderData
{
EntryDate = new DateTime(2013, 9, 5)
},
};
I now need to group by date range like so:
Today (1) <- contains the date 9/17/2013 and count of 1
within 2 weeks (3) <- contains dates 9/15,9/11,9/5 and count of 3
More than 2 weeks (2) <- contains dates 8/26, 1/1 and count of 2
this is my LINQ statement which doesn't achieve what I need but i think i'm in the ballpark (be kind if I'm not):
var defaultGroups = from l in ListHeader
group l by l.EntryDate into g
orderby g.Min(x => x.EntryDate)
select new { GroupBy = g };
This groups by individual dates, so I have 6 groups with 1 date in each. How do I group by date range , count and sort within each group?
Introduce array, which contains ranges you want to group by. Here is two ranges - today (zero days) and 14 days (two weeks):
var today = DateTime.Today;
var ranges = new List<int?> { 0, 14 };
Now group your items by range it falls into. If there is no appropriate range (all dates more than two weeks) then default null range value will be used:
var defaultGroups =
from h in ListHeader
let daysFromToday = (int)(today - h.EntryDate).TotalDays
group h by ranges.FirstOrDefault(range => daysFromToday <= range) into g
orderby g.Min(x => x.EntryDate)
select g;
UPDATE: Adding custom ranges for grouping:
var ranges = new List<int?>();
ranges.Add(0); // today
ranges.Add(7*2); // two weeks
ranges.Add(DateTime.Today.Day); // within current month
ranges.Add(DateTime.Today.DayOfYear); // within current year
ranges.Sort();
How about doing this?
Introduce a new property for grouping and group by that.
class ListHeaderData
{
public DateTime EntryDate;
public int DateDifferenceFromToday
{
get
{
TimeSpan difference = DateTime.Today - EntryDate.Date;
if (difference.TotalDays == 0)//today
{
return 1;
}
else if (difference.TotalDays <= 14)//less than 2 weeks
{
return 2;
}
else
{
return 3;//something else
}
}
}
}
Edit: as #servy pointed in comments other developers may confuse of int using a enum will be more readable.
So, modified version of your class would look something like this
class ListHeaderData
{
public DateTime EntryDate;
public DateRange DateDifferenceFromToday
{
get
{
//I think for this version no comments needed names are self explanatory
TimeSpan difference = DateTime.Today - EntryDate.Date;
if (difference.TotalDays == 0)
{
return DateRange.Today;
}
else if (difference.TotalDays <= 14)
{
return DateRange.LessThanTwoWeeks;
}
else
{
return DateRange.MoreThanTwoWeeks;
}
}
}
}
enum DateRange
{
None = 0,
Today = 1,
LessThanTwoWeeks = 2,
MoreThanTwoWeeks = 3
}
and use it like this
var defaultGroups = from l in ListHeader
group l by l.DateDifferenceFromToday into g // <--Note group by DateDifferenceFromToday
orderby g.Min(x => x.EntryDate)
select new { GroupBy = g };
Do you specifically want to achieve the solution in this way? Also do you really want to introduce spurious properties into your class to meet these requirements?
These three lines would achieve your requirements and for large collections willbe more performant.
var todays = listHeader.Where(item => item.EntryDate == DateTime.Today);
var twoWeeks = listHeader.Where(item => item.EntryDate < DateTime.Today.AddDays(-1)
&& item.EntryDate >= DateTime.Today.AddDays(-14));
var later = listHeader.Where(item => item.EntryDate < DateTime.Today.AddDays(-14));
also you then get the flexibility of different groupings without impacting your class.
[Edit: in response to ordering query]
Making use of the Enum supplied above you can apply the Union clause and OrderBy clause Linq extension methods as follows:
var ord = todays.Select(item => new {Group = DateRange.Today, item.EntryDate})
.Union(
twoWeeks.Select(item => new {Group = DateRange.LessThanTwoWeeks, item.EntryDate}))
.Union(
later.Select(item => new {Group = DateRange.MoreThanTwoWeeks, item.EntryDate}))
.OrderBy(item => item.Group);
Note that I'm adding the Grouping via a Linq Select and anonymous class to dynamically push a Group property again not effecting the original class. This produces the following output based on the original post:
Group EntryDate
Today 17/09/2013 00:00:00
LessThanTwoWeeks 11/09/2013 00:00:00
LessThanTwoWeeks 15/09/2013 00:00:00
LessThanTwoWeeks 05/09/2013 00:00:00
MoreThanTwoWeeks 26/08/2013 00:00:00
MoreThanTwoWeeks 01/01/2013 00:00:00
and to get grouped date ranges with count:
var ord = todays.Select(item => new {Group = DateRange.Today, Count=todays.Count()})
.Union(
twoWeeks.Select(item => new {Group = DateRange.LessThanTwoWeeks, Count=twoWeeks.Count()}))
.Union(
later.Select(item => new {Group = DateRange.MoreThanTwoWeeks, Count=later.Count()}))
.OrderBy(item => item.Group);
Output is:
Group Count
Today 1
LessThanTwoWeeks 3
MoreThanTwoWeeks 2
I suppose this depends on how heavily you plan on using this. I had/have a lot of reports to generate so I created a model IncrementDateRange with StartTime, EndTime and TimeIncrement as an enum.
The time increment handler has a lot of switch based functions spits out a list of times between the Start and End range based on hour/day/week/month/quarter/year etc.
Then you get your list of IncrementDateRange and in linq something like either:
TotalsList = times.Select(t => new RetailSalesTotalsListItem()
{
IncrementDateRange = t,
Total = storeSales.Where(s => s.DatePlaced >= t.StartTime && s.DatePlaced <= t.EndTime).Sum(s => s.Subtotal),
})
or
TotalsList = storeSales.GroupBy(g => g.IncrementDateRange.StartTime).Select(gg => new RetailSalesTotalsListItem()
{
IncrementDateRange = times.First(t => t.StartTime == gg.Key),
Total = gg.Sum(rs => rs.Subtotal),
}).ToList(),