linq weekly grouping? - c#

Linq
from c in customerReadings
group c by new { date = GetGroupingDateKey(DateRangeType, c.ReadDate), name = c.Name } into g
select new MeterReadingsForChart
{
ReadDate = g.Key.date,
Name = g.Key.name,
Value = g.Sum(y => y.Value),
TimeInterval = DateRangeType
};
GetGroupingDateKey()
private static DateTime GetGroupingDateKey(MeterReadingsTimeIntervals DateRangeType, DateTime Date)
{
DateTime date = new DateTime();
switch (DateRangeType)
{
case MeterReadingsTimeIntervals.Hourly:
//For Example data is betweet 10:05 - 11:05
//DateTime offSet = Date.AddMinutes(-5);
//date = new DateTime(offSet.Year, offSet.Month, offSet.Day, offSet.Hour, 5, 0);
date = new DateTime(Date.Year, Date.Month, Date.Day, Date.Hour, 0, 0);
break;
case MeterReadingsTimeIntervals.Daily:
date = new DateTime(Date.Year, Date.Month, Date.Day, 0, 0, 0);
break;
case MeterReadingsTimeIntervals.Weekly:
date = Date.AddDays(-((7 + Date.DayOfWeek - DayOfWeek.Monday) % 7));
break;
case MeterReadingsTimeIntervals.Monthly:
date = new DateTime(Date.Year, Date.Month, 1, 0, 0, 0);
break;
case MeterReadingsTimeIntervals.Yearly:
date = new DateTime(Date.Year, 1, 1, 0, 0, 0);
break;
}
return date;
}
Records time : 10/10/2012 - between 09:15 and 10:15
Hourly groupped data count : 2 (expected)
Daily groupped data count : 1 (expected)
Weekly groupped data count : 5 (group count (not expected))
Monthly groupped data count : 1 (expected)
Yearly groupped data count : 1 (expexted)
How can I group records by week. What is the wrong about my weekly grouping?
Thanks in advance.

I would imagine your Date has a time part - you need to zero it out, e.g.
date = Date.Date.AddDays(-((7 + Date.DayOfWeek - DayOfWeek.Monday) % 7));

Related

How to find matching times between a date range

I'm doing an appointment finder which looks through people's calendars and searches for available time for meetings. It works very fine. However, I wanted to exclude the lookup for the break times, which are a DateRange between two DateTimes which both have 1/1/1 as the year (as placeholder) and include the time behind it. I'm iterating through each minute of a working day and a function should determine whether the current time is in the break hours DateRange.
I have this List with a custom DateRange class:
private static List<DateRange> _breakTimes = new List<DateRange>() {
new DateRange(new DateTime(1, 1, 1, 10, 0, 0), new DateTime(1, 1, 1, 10, 30, 0)),
new DateRange(new DateTime(1, 1, 1, 13, 30, 0), new DateTime(1, 1, 1, 14, 0, 0))
};
And here's the function that iterates through all working days (I have commented the line where the check function should go):
foreach (var date in GetWorkingDays(startDate, endDate))
{
for (int hour = 0; hour <= 24; hour++)
{
if (hour < startingHour || hour >= endingHour) continue;
for (int minute = 0; minute < 60; minute++)
{
var currentDate = new DateTime(date.Year, date.Month, date.Day, hour, minute, 0);
bool shouldContinue = false;
foreach (var breakTime in _breakTimes)
{
// Here it should look wheter the current time is in the break times:
if ((currentDate.Hour >= breakTime.StartDate.Hour && currentDate.Hour <= breakTime.EndDate.Hour) && (currentDate.Minute > breakTime.StartDate.Minute && currentDate.Minute <= breakTime.EndDate.Minute))
{
shouldContinue = true;
break;
}
}
if (shouldContinue) continue;
if (minute == 59)
{
_workingDaysWithHours.Add(new DateRange(new DateTime(date.Year, date.Month, date.Day, hour, minute, 0), new DateTime(date.Year, date.Month, date.Day, hour + 1, 0, 0)));
continue;
}
_workingDaysWithHours.Add(new DateRange(new DateTime(date.Year, date.Month, date.Day, hour, minute, 0), new DateTime(date.Year, date.Month, date.Day, hour, minute + 1, 0)));
}
}
}
So I only need to know how to check whether the current days' time is between a break time.
Hope it's understandable.
You can modify the code within this example to get what you want Algorithm to detect overlapping periods
foreach (var date in new [] { DateTime.Now })
{
for (int hour = 0; hour <= 24; hour++)
{
if (hour < startingHour || hour >= endingHour) continue;
for (int minute = 0; minute < 60; minute++)
{
var currentDate = new DateTime(date.Year, date.Month, date.Day, hour, minute, 0);
bool shouldContinue = false;
var currentTime = new DateTime(1, 1, 1, hour, minute, 0);
foreach (var breakTime in _breakTimes)
{
var isInBreak = breakTime.StartDate <= currentTime && currentTime < breakTime.EndDate;
if (isInBreak)
{
shouldContinue = true;
break;
}
}
if (shouldContinue) continue;
if (minute == 59)
{
_workingDaysWithHours.Add(new DateRange(new DateTime(date.Year, date.Month, date.Day, hour, minute, 0), new DateTime(date.Year, date.Month, date.Day, hour + 1, 0, 0)));
continue;
}
_workingDaysWithHours.Add(new DateRange(new DateTime(date.Year, date.Month, date.Day, hour, minute, 0), new DateTime(date.Year, date.Month, date.Day, hour, minute + 1, 0)));
}
}
}
I solved it by using a List with Tuples and add the minutes seperatly:
private static List<Tuple<TimeSpan, int>> _breakTimes = new List<Tuple<TimeSpan, int>>()
{
new Tuple<TimeSpan, int>(new TimeSpan(10, 0, 0), 30),
new Tuple<TimeSpan, int>(new TimeSpan(13, 30, 0), 30)
};
And this is the function looking whether it's a break time:
foreach (var breakTime in _breakTimes)
{
if (currentTime >= breakTime.Item1 && currentTime < breakTime.Item1.Add(new TimeSpan(0, breakTime.Item2, 0)))
{
shouldContinue = true;
break;
}
}

Check if any dates within a range fall within a specified month

I am at a bit of a loss as to the best approach. Let's say I have the following:
//Get the first and last day of the month -- ex February
int month = DateTime.ParseExact("February", "MMMM", new CultureInfo("en-US")).Month;
var now = DateTime.Now;
var firstOfMonth = new DateTime(now.Year, month, 1);
var lastOfMonth = new DateTime(now.Year, month, DateTime.DaysInMonth(now.Year, month));
var coverageStartDate = new DateTime(now.Year, 1, 1);
var coverageEndDate = new DateTime(now.Year, 2, 15);
I am trying to create a check to see if the date range of coverageStartDate and coverageEndDate falls in the month of February. Keep in mind that the values could also look like:
var coverageStartDate = new DateTime(now.Year, 2, 3);
var coverageEndDate = new DateTime(now.Year, 2, 10);
As long as there is a single date in the coverage start / end date range falls in the month of February, I would want to return true.
With DateTime you can use >=, <=, etc.
Adopting your code, I would do something like this:
var firstOfMonth = new DateTime(now.Year, month, 1, 0, 0, 0);
var lastOfMonth = new DateTime(now.Year, month, DateTime.DaysInMonth(now.Year, month), 23, 59, 59);
var coverageStartDate = new DateTime(now.Year, 1, 1);
var coverageEndDate = new DateTime(now.Year, 2, 15);
if(coverageStartDate <= lastOfMonth && coverageEndDate >= firstOfMonth)
{
// Do something
}
This code does not requires that years have to be the same, so the coverage time can span multiple years.

How Can I Only Work Hours DateTime and Time Addition

My code below.I want to do user select 10.06.2015 09:00 - 12.06.2015 13:00 after I will show 2 days 2 hours.
But I want to do Working days and Working Hours beetween 09:00 - 18:00 well users when you 10.06.2015 09:00 - 12.06.2015 13:00 I want to show only 2,5 days.
How can I do?
DateTime t1 = dateTimePicker1.Value.Date;
DateTime t2 = dateTimePicker2.Value.Date;
string s1 = textBox9.Text;
string s2 = textBox10.Text;
DateTime dt1 = t1.AddMinutes(DateTime.Parse(s1).TimeOfDay.TotalMinutes);
DateTime dt2 = t2.AddMinutes(DateTime.Parse(s2).TimeOfDay.TotalMinutes);
var fark = dt2 - dt1;
label1.Text =
String.Format("{0}{1}{2}",
fark.Days > 0 ? string.Format("{0} gün", fark.Days) : "",
fark.Hours > 0 ? string.Format("{0} saat ", fark.Hours) : "",
fark.Minutes > 0 ? string.Format("{0} dakika ", fark.Minutes) : "").Trim();
Well you can assume that any days in the range, except the first and last are full working days. So you need (AllDaysInRange -2) + HoursInfirstDay + HoursInLastDay.
TimeSpan ts = t2 - t1;
ts.Days = ts.Days - 2; //Allow for the 2 end days
int Day1Hours = t1.Hours - 9;//This removes any hours between 00.00 and 09.00
if (day1Hours > 9) //Then the user was working past 18.00
ts.Days = ts.Days+1
else
ts.Hours = ts.Hours + day1Hours;
int Day2Hours = t2.Hours - 9;//This removes any hours between 00.00 and 09.00
if (day2Hours > 9) //Then the user was working past 18.00
ts.Days = ts.Days+1
else
ts.Hours = ts.Hours + day2Hours;
If you can get this to work (I have written it from memory), then I'd wrap the code to convert the hours of the end days into a method rather than repeating it.
According to DateTime Picker In WinForm How To Pick Time? post, you can change your DateTimePicker, to function with times as well.
To limit the range which the users can select, you can modify your ValueChanged event or write your own validation for it.
Probably the simplest is:
private void dateTimePicker1_ValueChanged(object sender, EventArgs e)
{
if (dateTimePicker1.Value.Hour < 10) // the 10 is just a random number, you can change it to your own limit
dateTimePicker1.Value = this.dateTimePicker1.Value.AddHours(10 - dateTimePicker1.Value.Hour);
}
To calculate your 2,5 day according to the working hours, i would write a function to handle this responsibility like:
private int TimeInWorkingHours(DateTime start, DateTime end, int firstHour, int lastHour)
{
int days = Math.Min(end.Subtract(start).Days - 2, 0) ;
int hoursInADay = lastHour - firstHour;
int result = days * hoursInADay;
result += start.Hour - firstHour;
result += lastHour - end.Hour;
return result;
}
This way you call TimeInWorkingHours(...) function with your start date and end date, also providing your first and last working hours.
First you calculate the worked days, than add the border hours. This way you get your worked hours which you can then divide by the work hours to get your working days.
try this
private void GetProperOfficeHours(ref DateTime date)
{
int minHour = 9, maxHour = 17;
if (date.Hour < minHour) //if earlier than office hours - start from 9am
{
date = date + new TimeSpan(9, 0, 0);
}
else if (date.Hour > maxHour) //if later than office hours - go to next day 9am
{
date = date.AddDays(1) + new TimeSpan(9, 0, 0);
}
}
Then ...
//assuming firstDate & lastDate have date and time
int[] weekendDays = new int[2] { 0, 6 }; // Sunday and Saturday
GetProperOfficeHours(ref firstDate);
GetProperOfficeHours(ref lastDate);
while (weekendDays.Contains((int)firstDate.DayOfWeek))
{
//get next date
firstDate = firstDate.AddDays(1);
}
while (weekendDays.Contains((int)lastDate.DayOfWeek))
{
//get prev date
lastDate = lastDate.AddDays(-1);
}
double hourDiff = Math.Abs(firstDate.Hour - lastDate.Hour) / 8.0; //8 office hours
double dayDifference = 0;
while (firstDate.Date <= lastDate.Date) //Loop and skip weekends
{
if (!weekendDays.Contains((int)firstDate.DayOfWeek)) //can also check for holidays here
dayDifference++;
firstDate = firstDate.AddDays(1);
}
dayDifference = dayDifference + hourDiff;
May need some tweaking, hope you find it helpful.
Try this
bool IsWorkingDay(DateTime dt)
{
int year = dt.Year;
Dictionary<DateTime, object> holidays = new Dictionary<DateTime, object>();
holidays.Add(new DateTime(year, 1, 1), null);
holidays.Add(new DateTime(year, 1, 6), null);
holidays.Add(new DateTime(year, 4, 25), null);
holidays.Add(new DateTime(year, 5, 1), null);
holidays.Add(new DateTime(year, 6, 2), null);
holidays.Add(new DateTime(year, 8, 15), null);
holidays.Add(new DateTime(year, 11, 1), null);
holidays.Add(new DateTime(year, 12, 8), null);
holidays.Add(new DateTime(year, 12, 25), null);
holidays.Add(new DateTime(year, 12, 26), null);
DateTime easterMonday = EasterSunday(year).AddDays(1);
if (!holidays.ContainsKey(easterMonday))
holidays.Add(easterMonday, null);
if (!holidays.ContainsKey(dt.Date))
if (dt.DayOfWeek > DayOfWeek.Sunday && dt.DayOfWeek < DayOfWeek.Saturday)
return true;
return false;
}
string WorkingTime(DateTime dt1, DateTime dt2)
{
// Adjust begin datetime
if (IsWorkingDay(dt1))
{
if (dt1.TimeOfDay < TimeSpan.FromHours(9))
dt1 = new DateTime(dt1.Year, dt1.Month, dt1.Day, 9, 0, 0);
else if (dt1.TimeOfDay > TimeSpan.FromHours(13) && dt1.TimeOfDay < TimeSpan.FromHours(14))
dt1 = new DateTime(dt1.Year, dt1.Month, dt1.Day, 14, 0, 0);
else if (dt1.TimeOfDay > TimeSpan.FromHours(18))
dt1 = new DateTime(dt1.Year, dt1.Month, dt1.Day, 9, 0, 0).AddDays(1);
}
else
dt1 = new DateTime(dt1.Year, dt1.Month, dt1.Day, 9, 0, 0).AddDays(1);
// Adjust end datetime
if (IsWorkingDay(dt2))
{
if (dt2.TimeOfDay < TimeSpan.FromHours(9))
dt2 = new DateTime(dt2.Year, dt2.Month, dt2.Day, 18, 0, 0).AddDays(-1);
else if (dt2.TimeOfDay > TimeSpan.FromHours(18))
dt2 = new DateTime(dt2.Year, dt2.Month, dt2.Day, 18, 0, 0);
else if (dt2.TimeOfDay > TimeSpan.FromHours(13) && dt2.TimeOfDay < TimeSpan.FromHours(14))
dt2 = new DateTime(dt2.Year, dt2.Month, dt2.Day, 13, 0, 0);
}
else
dt2 = new DateTime(dt2.Year, dt2.Month, dt2.Day, 18, 0, 0).AddDays(-1);
double days = 0;
double hours = 0;
double minutes = 0;
if (dt2 > dt1)
{
// Move dt1 forward to reach dt2 day chacking for working days
while (dt1.DayOfYear < dt2.DayOfYear)
{
if (IsWorkingDay(dt1))
days++;
dt1 = dt1.AddDays(1);
}
// Now get the worked hours as if were on the same day in the same manner
TimeSpan sdwt = dt2 - dt1;
if (dt1.TimeOfDay < TimeSpan.FromHours(13) && dt2.TimeOfDay > TimeSpan.FromHours(14))
sdwt -= TimeSpan.FromHours(1);
if (sdwt == TimeSpan.FromHours(8))
days++;
else
{
hours = sdwt.Hours;
minutes = sdwt.Minutes;
}
}
// There is a pause in between so adjust if the interval include it
var totalminutes = (days * 8 * 60 + hours * 60 + minutes);
string res = String.Format("{0} days {1} hours {2} minutes",
days,
hours,
minutes);
string totRes = String.Format("{0} days {1} hours {2} minutes",
totalminutes / 8 / 60,
totalminutes / 8,
totalminutes);
return res + "\r\n" + totRes;
}

Linq Group by specific day interval?

I have following line to group my collection:
group c by new { date = GetGroupingDateKey(DateRangeType, c.ReadDate), name = c.Name } into g
and I use following function to get grouping date key:
private static DateTime GetGroupingDateKey(MeterReadingsTimeIntervals DateRangeType, DateTime Date)
{
DateTime date = new DateTime();
switch (DateRangeType)
{
case MeterReadingsTimeIntervals.Hourly:
date = new DateTime(Date.Year, Date.Month, Date.Day, Date.Hour, 0, 0);
break;
case MeterReadingsTimeIntervals.Daily:
date = new DateTime(Date.Year, Date.Month, Date.Day, 0, 0, 0);
break;
case MeterReadingsTimeIntervals.Weekly:
// ???
break;
case MeterReadingsTimeIntervals.Monthly:
date = new DateTime(Date.Year, Date.Month, 1, 0, 0, 0);
break;
case MeterReadingsTimeIntervals.Yearly:
date = new DateTime(Date.Year, 1, 1, 0, 0, 0);
break;
}
return date;
}
But I dont know about weekly grouping(it may be specific day intervals, 10 days,15 days, etc.). How can I group weekly? Should I use another way to group data?
Thanks in advice.
Maybe like this:
date = Date.AddDays(-((Date.DayOfWeek - DayOfWeek.Monday +7)%7));
date = new DateTime(date.Year, date.Month, date.Day, 0, 0, 0);
If you consider Monday start of the week.
You can try it using this code:
EDIT Code edited after #Rawling suggestion, check the comments
DateTime dt = DateTime.Now;
dt = dt.AddDays(-((dt.DayOfWeek - DayOfWeek.Monday + 7)%7));
dt = new DateTime(dt.Year, dt.Month, dt.Day, 0, 0, 0);//today is Friday and it will show Monday at the end.
With the helper function
int DaysFromLast(DateTime date, DayOfWeek dow)
{
return (7 + date.DayOfWeek - dow) % 7;
}
you can use
var date = Date.Date;
date = date.AddDays(-DaysFromLast(date, DayOfWeek.Monday));
for a week starting on a Monday, and so on.
For arbitrary intervals, you'll need a constant start date (preferably back in the past); then
var date = Date.Date;
date = date.AddDays(-((date - startDate).Days % numberOfDaysInInterval));
The week is dependent on the kind of calendar that you're using and its rules. Some have the first week of the year start on the first Monday of the year, some on Sunday, etc, there are differences.
You can use GregorianCalendar for instance and its method GetWeekOfYear(DateTime).
case MeterReadingsTimeIntervals.Weekly:
var gc = new GregorianCalendar();
return gc.GetWeekOfYear(Date);
Now for this to work you have to make the return type of your method object or force the weekinfo into the same DateTime (set it to Monday of the found week),

Get next date within a quarter

I need to create a function to return the next processing date for a given item. An item has a number that represents the month within a quarter that it is processed, as well as a number that represents the week within that month when it is processed. So, given a particular item's create date, I need to get the next processing date for that item, which will be the first day of it's assigned week and month within a quarter.
Note that weeks are broken out by 7 days from the start of the month, regardless of what day of the week. So the first day of the first week could start on Tuesday or any other day for the purposes of this calculation.
Example:
Let's say I have an item with a completed date of 1/8/2010. That item has a monthWithinQuarter value of 2. It has a weekWithinMonth value of 3. So for this item that resolves to the third week of February, so I would want the function to return a date of 2/15/2010.
The function should look something like this:
var nextProcessingDate = GetNextProcessingDate(
itemCompletedDate,
monthWithinQuarter,
weekWithinMonth);
This calculation has to be pretty fast as this calculation is going to be happening a lot, both in real time to display on a web site as well as in batch mode when processing items.
Thanks,
~ Justin
Okay, this should do it for you:
static DateTime GetNextProcessingDate(
DateTime itemCompletedDate,
int monthWithinQuarter,
int weekWithinMonth
) {
if (monthWithinQuarter < 1 || monthWithinQuarter > 3) {
throw new ArgumentOutOfRangeException("monthWithinQuarter");
}
if (weekWithinMonth < 1 || weekWithinMonth > 5) {
throw new ArgumentOutOfRangeException("weekWithinMonth");
}
int year = itemCompletedDate.Year;
DateTime[] startOfQuarters = new[] {
new DateTime(year, 1, 1),
new DateTime(year, 4, 1),
new DateTime(year, 7, 1),
new DateTime(year, 10, 1)
};
DateTime startOfQuarter = startOfQuarters.Where(d => d <= itemCompletedDate)
.OrderBy(d => d)
.Last();
int month = startOfQuarter.Month + monthWithinQuarter - 1;
int day = (weekWithinMonth - 1) * 7 + 1;
if (day > DateTime.DaysInMonth(year, month)) {
throw new ArgumentOutOfRangeException("weekWithinMonth");
}
DateTime candidate = new DateTime(year, month, day);
if (candidate < itemCompletedDate) {
month += 3;
if(month > 12) {
year++;
month -= 12;
}
}
return new DateTime(year, month, day);
}
As far as efficiency, the place where I see the most room for improvement is repeatedly creating the array
DateTime[] startOfQuarters = new[] {
new DateTime(year, 1, 1),
new DateTime(year, 4, 1),
new DateTime(year, 7, 1),
new DateTime(year, 10, 1)
};
So let's offload that to a method and memoize it:
static Dictionary<int, DateTime[]> cache = new Dictionary<int, DateTime[]>();
public static DateTime[] StartOfQuarters(DateTime date) {
int year = date.Year;
DateTime[] startOfQuarters;
if(!cache.TryGetValue(year, out startOfQuarters)) {
startOfQuarters = new[] {
new DateTime(year, 1, 1),
new DateTime(year, 4, 1),
new DateTime(year, 7, 1),
new DateTime(year, 10, 1)
};
cache.Add(year, startOfQuarters);
}
return startOfQuarters;
}
If you don't need the flexibility of quarters possibly starting on unusual days, you could replace
DateTime[] startOfQuarters = new[] {
new DateTime(year, 1, 1),
new DateTime(year, 4, 1),
new DateTime(year, 7, 1),
new DateTime(year, 10, 1)
};
DateTime startOfQuarter = startOfQuarters.Where(d => d <= itemCompletedDate).OrderBy(d => d).Last();
int month = startOfQuarter.Month + monthWithinQuarter - 1;
with
int month = 3 * ((itemCompletedDate.Month - 1) / 3) + monthWithinQuarter;
From what I understand, this should do the job:
public static DateTime GetNextProcessingDate(DateTime itemCreationDate, int monthWithinQuarter,
int weekWithinMonth)
{
var quarter = (itemCreationDate.Month - 1) / 4; // Assumes quarters are divided by calendar year.
var month = quarter * 4 + monthWithinQuarter; // First quarter of month plus month within quarter
var dayInMonth = (weekWithinMonth - 1) * 7 + 1; // Weeks are counted from first day, regardless of day of week (as you mention).
return new DateTime(itemCreationDate.Year, month, dayInMonth);
}
Let me know if any of it isn't clear.
Your calculation, I suppose should be reduced at
DateTime dt;
dt.AddDays(daysToAdd);
dt.AddMonths(monthsToAdd);
dt.AddHours(hoursToAdd);
dt.AddYears(yearsToAdd);

Categories

Resources