Recurrence of events (LINQ query) - c#

I have DateStart, DateEnd Periodicity, TypePeriodicity fields.
We have a query:
var result = Events.Where(e => e.DateStart <=today && e.DateEnd >= today).ToList();
I want that this query to check Periodicity.
For example:
name - record1
DateStart = 2012-02-02
DateEnd = 2012-03-31
Periodicity = 2
TypePeriodicity = 1 ( it's mean a week, may be also day = 0, month=2):
I want the following, if current date equals:
2,3,4,5 February - return `record1`
6,7,8..12 - not return, because TypePeriodicity = 1 and Periodicity = 2, which means every 2 weeks
13..19 - return `record1`
20..26 - not return
and so on until `DateEnd`
Thanks.
PS. Maybe not LINQ, but simple method that recieve result as parameter.

Here is something to get you started:
You could define a DateEvaluator delegate like so:
delegate bool DateEvaluator(DateTime startDate, DateTime endDate, DateTime dateToCheck, int periodicity);
The purpose of the delegate would be to evaluate for a given periodicity type if a date should be considered as within range. We would have hence 3 date evaluators.
One for each period type: Lets call them dayPeriodicityChecker, weekPeriodicityChecker and monthPeriodicityChecker
Our dayPeriodicityChecker is straightforward:
DateEvaluator dayPeriodicityChecker = (startDate, endDate, dateToCheck, periodicity) =>
{
if ((dateToCheck < startDate) || (dateToCheck > endDate))
return false;
TimeSpan dateDiff = dateToCheck - startDate;
return dateDiff.Days % periodicity == 0;
};
Our weekPeriodicityChecker needs to account for the start day of week, so the start date would need to be adjusted to the date in which the startDate week actually starts:
DateEvaluator weekPeriodicityChecker = (startDate, endDate, dateToCheck, periodicity) =>
{
if ((dateToCheck < startDate) || (dateToCheck > endDate))
return false;
DateTime adjustedStartDate = startDate.AddDays(-(int)startDate.DayOfWeek + 1);
TimeSpan dateDiff = dateToCheck - adjustedStartDate;
return (dateDiff.Days / 7) % periodicity == 0;
};
Our monthPeriodicityChecker needs to cater for months with a variable number of days:
DateEvaluator monthPeriodicityChecker dateToCheck, periodicity) =>
{
if ((dateToCheck < startDate) || (dateToCheck > endDate))
return false;
int monthDiff = 0;
while (startDate.AddMonths(1) < dateToCheck)
{
monthDiff++
// i'm sure there is a speedier way to calculate the month difference, but this should do for the purpose of this example
}
return (monthDiff - 1) % periodicity == 0;
};
Once you have all your date evaluators defined you could put them in an array like so:
DateEvaluator[] dateEvaluators = new DateEvaluator[]
{
dayPeriodicityChecker,
weekPeriodicityChecker,
monthPeriodicityChecker
};
This will allow you to do :
int periodicityType = 0; // or 1=week or 2=months
bool isDateIn = dateEvaluators[periodicityType ](startDate, endDate, dateTocheck, Periodicity)
So lets test this:
PeriodicityEvent pEvent = new PeriodicityEvent
{
Name = "record1",
DateStart = new DateTime(2012, 02, 02),
DateEnd = new DateTime(2012, 03, 31),
PeriodicityType = 1,
Periodicity = 2
};
DateTime baseDate = new DateTime(2012, 02, 01);
for (int i = 0; i < 30; i++)
{
DateTime testDate = baseDate.AddDays(i);
if (dateEvaluators[pEvent.PeriodicityType](pEvent.DateStart, pEvent.DateEnd, testDate, pEvent.Periodicity))
{
Console.WriteLine("{0} is in", testDate.ToString("dd MMM"));
}
else
{
Console.WriteLine("{0} is out", testDate.ToString("dd MMM"));
}
}
This will produce the desired output as below:
To use you would simply do:
Events.Where(e => dateEvaluators[e.PeriodType](e.DateStart, e.DateEnd, today, e.Periodicity).ToList();
Good luck!

Related

Find next 5 working days starting from today

I use nager.date to know if a day is a holiday day or a weekend day Saturday and Sunday).
I need to extract the date (starting from today or any other date) after 5 working days.
DateTime date1 = new DateTime(2019, 12, 23);
int i = 0;
while ( i < 5)
{
if (DateSystem.IsPublicHoliday(date1, CountryCode.IT) || DateSystem.IsWeekend(date1, CountryCode.IT))
{
date1 = date1.AddDays(1);
}
else
{
date1= date1.AddDays(1);
i++;
}
}
The problem of this code is that if the last else occurs, it add me 1 day but without doing any other check.
For example:
If the start date is 13/07/2020, the result will be at the end 18/07/2020 and as you can see is on Saturday.
How could I modify this code to achieve what I need?
The order is important. The AddDays should be called first, and after it is called we check if the new day matches our criteria.
Note: I have renamed the i variable so it is more clear.
DateTime date1 = new DateTime(2019, 12, 23);
int daysAdded = 0;
while (daysAdded < 5)
{
date1 = date1.AddDays(1);
if (!DateSystem.IsPublicHoliday(date1, CountryCode.IT) && !DateSystem.IsWeekend(date1, CountryCode.IT)) {
// We only consider laboral days
// laboral days: They are not holidays and are not weekends
daysAdded ++;
}
}
I always try to generalize my solutions, so here's one enabling LINQ:
public bool IsWorkingDay(DateTime dt)
=> !DateSystem.IsPublicHoliday(dt) && !DateSystem.IsWeekend(dt);
public DateTime NextWorkingDay(DateTime dt)
{
dt = dt.AddDays(1);
while (!IsWorkingDay(dt))
dt = dt.AddDays(1);
return dt;
}
public IEnumerable<DateTime> WorkingDaysFrom(DateTime dt)
{
if (!IsWorkingDay(dt))
dt = NextWorkingDay(dt); // includes initial dt, remove if unwanted
while (true)
{
yield return dt;
dt = NextWorkingDay(dt);
}
}
This will pump out working days from a given date until end of time, and then use LINQ to grab the number you want:
var next5 = WorkingDaysFrom(DateTime.Today).Take(5).ToList();
here's how to get all the working days in 2020:
var working2020 = WorkingDaysFrom(new DateTime(2020, 1, 1))
.TakeWhile(dt => dt.Year == 2020)
.ToList();
DateTime date1 = new DateTime(2019, 12, 23);
int i = 0;
while ( i < 5)
{
date1 = date1.AddDays(1);
if (!DateSystem.IsPublicHoliday(date1, CountryCode.IT) && !DateSystem.IsWeekend(date1, CountryCode.IT))
{
i++;
}
}
but I think that you need a DateTime[] to store all the five days
This is a better and a faster way to do this without using third party libraries.
DateTime nowDate = DateTime.Now;
DateTime expectedDate;
if (nowDate.DayOfWeek == DayOfWeek.Saturday)
{
expectedDate = nowDate.AddDays(6);
}
else if (nowDate.DayOfWeek == DayOfWeek.Sunday)
{
expectedDate = nowDate.AddDays(5);
}
else
{
expectedDate = nowDate.AddDays(7);
}
I thought about the problem, and based on the LINQ suggestion Lasse-v-Karlsen made, developed this code, which gives you most flexibility:
void Main()
{
// a list of public holidays
var holidays = new List<DateTime>() {new DateTime(2020,1,1),
new DateTime(2020,12,24), new DateTime(2020,12,25), new DateTime(2020,12,26)};
// a function checking if the date is a public holiday
Func<DateTime, bool> isHoliday = (dt) => holidays.Any(a=>a==dt);
// the start date
var dt = new DateTime(2020, 07, 13);
// end date, 5 working days later
var endDate = GetWorkingDay(dt, 5, isHoliday);
// print it
Console.WriteLine(endDate?.ToString("yyyy-mm-dd"));
}
public DateTime? GetWorkingDay(DateTime dt, int skipWorkingDays = 0,
Func<DateTime, bool> holidays=null)
{
if (holidays == null) holidays = (dt) => false;
IEnumerable<DateTime> NextWorkingDay(DateTime dt)
{
while (true)
{
var day = dt.DayOfWeek;
if (day != DayOfWeek.Saturday && day != DayOfWeek.Sunday
&& !holidays.Invoke(dt)) yield return dt;
dt = dt.AddDays(1);
}
}
if (skipWorkingDays<0) return null;
if (skipWorkingDays==0) return NextWorkingDay(dt).First();
var nextXDays = NextWorkingDay(dt).Take(skipWorkingDays).ToList();
var endDate = nextXDays.OrderByDescending(d => d).First();
return endDate;
}
Whether you have a list of public holidays like in this example, or a function coming from a library telling you if a date is a public holiday or not, just feel free to modify the Lambda function isHoliday. In your case, it would be defined as:
Func<DateTime, bool> isHoliday = (dt) => DateSystem.IsPublicHoliday(dt, CountryCode.IT);

How to get the list of week start date (Monday) and end date (Sunday) for the year in C#

I want to get only weeks for the whole year where I want to get the start date (Monday) and end date (Friday) in C#.
For example: 1/52 = 02 Jan (Monday) - 09 Jan (Sunday) 2/52 = 10 Jan (Monday) - 17 Jan (Sunday)
and so on.
I can get current week dates but no idea how to get for the year.
// We Set the Monday as the first day of the week.
DayOfWeek day = datetime.DayOfWeek;
int days = day - DayOfWeek.Monday;
if (days == -1)
{
days = 6; // this is when we have sunday as a DayOfWeek day
}
DateTime start = datetime.AddDays(-days);
DateTime end = start.AddDays(6);
Without making it complicated you can simply use while like below.
while (datetime.DayOfWeek != DayOfWeek.Monday)
{
datetime= datetime.AddDays(1);
}
DateTime start = datetime;
DateTime end = start.AddDays(6);
Or you want to find week from the week index 1/52 for any year then write function like below. Use it like GetWeek(1, 2020) to get 06.01.2020 - 12.01.2020. Format it as per your requirement.
public DateTime GetNextMonday(DateTime datetime)
{
return datetime.AddDays((7 - (int)datetime.DayOfWeek + (int)DayOfWeek.Monday) % 7);
}
public string GetWeek(int week, int year)
{
var start = GetNextMonday(new DateTime(year, 1, 1).AddDays((week-1)*7));
var end = start.AddDays(6);
return start.ToShortDateString() + " - " + end.ToShortDateString();
}
As far as I have understood, probably this will help, I tried the below and it displayed for me the start and end dates for the specified years:
DateTime starting = new DateTime(2020, 1, 1);
DateTime ending = new DateTime(2020, 12, 1);
DateTime currentDay = starting;
DateTime start = currentDay;
DateTime end = currentDay;
while (ending.Year >= currentDay.Year)
{
if (currentDay.DayOfWeek == DayOfWeek.Monday)
{
start = currentDay;
end = start.AddDays(6);
currentDay = end;
Console.WriteLine(start + "(" + start.DayOfWeek + ")");
Console.WriteLine(end + "(" + end.DayOfWeek + ")");
}
else
{
currentDay = currentDay.AddDays(1);
}
}
You can use methods below to calculate start day of any week of any year
public static DateTime StartOfNthWeekOfYear(int year, int weekNumber, DayOfWeek firstDayOfWeek)
{
if(weekNumber < 1)
{
throw new ArgumentOutOfRangeException(nameof(weekNumber));
}
DateTime startOfWeek = StartOfFirstWeekOfYear(year, firstDayOfWeek).AddDays((weekNumber - 1) * 7);
DateTime endOfWeek = startOfWeek.AddDays(6);
if(endOfWeek.Year != year || startOfWeek.Year != year)
{
throw new ArgumentOutOfRangeException(nameof(weekNumber));
}
return startOfWeek;
}
public static DateTime StartOfFirstWeekOfYear(int year, DayOfWeek firstDayOfWeek)
{
DateTime startOfYear = new DateTime(year, 1, 1);
if (startOfYear.DayOfWeek != firstDayOfWeek)
{
return StartOfWeek(startOfYear, firstDayOfWeek).AddDays(7);
}
return startOfYear;
}
public static DateTime StartOfWeek(DateTime value, DayOfWeek firstDayOfWeek)
{
if (value.DayOfWeek != firstDayOfWeek)
{
return value.AddDays(-((7 + (int)value.DayOfWeek - (int)firstDayOfWeek) % 7));
}
return value;
}
I think this should work for Gregorian calendars and takes into account different cultures:
public static IList<DateTime> GetFirstDayOfWeekDates(CultureInfo cultureInfo, int year)
{
var lastDateOfYear = new DateTime(year, 12, 31);
var firstDate = new DateTime(year, 1, 1);
var dayOfWeek = cultureInfo.DateTimeFormat.FirstDayOfWeek;
while (firstDate.DayOfWeek != dayOfWeek)
{
firstDate = firstDate.AddDays(1);
}
var numberOfWeeksInYear = cultureInfo.Calendar.GetWeekOfYear(lastDateOfYear, cultureInfo.DateTimeFormat.CalendarWeekRule, dayOfWeek);
var firstDayOfWeekDates = new List<DateTime>();
firstDayOfWeekDates.Add(firstDate);
var currentDate = firstDate;
for (int i = 0; i < numberOfWeeksInYear; i++)
{
var weekLater = currentDate.AddDays(7);
if (weekLater.Year == year)
{
currentDate = weekLater;
firstDayOfWeekDates.Add(currentDate);
}
}
return firstDayOfWeekDates;
}
You can test this with a console app like this (make the method static):
static void Main(string[] args)
{
var ci = new CultureInfo("en-GB");
var dates = GetFirstDayOfWeekDates(ci, DateTime.Now.Year);
foreach (var dt in dates)
{
Console.WriteLine("Date: " + dt.ToShortDateString());
}
Console.ReadLine();
}
It brings back the following:
If you want to include the end date of the week as well then you can tweak this slightly by adding a new class called WeekDate:
public class WeekDate
{
public DateTime StartOfWeek { get; set; }
public DateTime EndOfWeek { get; set; }
}
GetFirstDayOfWeekDates then becomes:
public static IList<WeekDate> GetFirstDayOfWeekDates(CultureInfo cultureInfo, int year)
{
var lastDateOfYear = new DateTime(year, 12, 31);
var firstDate = new DateTime(year, 1, 1);
var dayOfWeek = cultureInfo.DateTimeFormat.FirstDayOfWeek;
while (firstDate.DayOfWeek != dayOfWeek)
{
firstDate = firstDate.AddDays(1);
}
var numberOfWeeksInYear = cultureInfo.Calendar.GetWeekOfYear(lastDateOfYear, cultureInfo.DateTimeFormat.CalendarWeekRule, dayOfWeek);
var firstDayOfWeekDates = new List<WeekDate>();
firstDayOfWeekDates.Add(new WeekDate { StartOfWeek = firstDate, EndOfWeek = firstDate.AddDays(6) });
var currentDate = firstDate;
for (int i = 0; i < numberOfWeeksInYear; i++)
{
var weekLater = currentDate.AddDays(7);
if (weekLater.Year == year)
{
currentDate = currentDate.AddDays(7);
firstDayOfWeekDates.Add(new WeekDate { StartOfWeek = currentDate, EndOfWeek = currentDate.AddDays(6) });
}
}
return firstDayOfWeekDates;
}
Which returns:

Perform calculations on a set using only LINQ

I'd like to perform the following using only LINQ.
I have a list of time sheet entries with user's in and out times. The class looks like this:
public class TimeSheetLog
{
public Guid EmployeeId { get; set; }
public DateTime ClockInTimeStamp { get; set; }
public DateTime ClockOutTimeStamp { get; set; }
}
I'm passing a List<TimeSheetLog>() which contains all logs from the beginning of the year to date.
I'm trying to calculate the total work time -- regardless of employee -- for the month of January. Please also notice that I have a function named GetTimeDifferenceInMinutes() which calculates the number of minutes between two date/time values.
Here's what I currently have but I feel the whole thing can be done using LINQ only.
public static int GetTotalTimeWorked(List<TimeSheetLog> logs, DateTime startDate, DateTime endDate)
{
// I'm passing 1/1/2018 for startDate and 1/31/2018 for endDate to this function
var totalTimeWorkedInMinutes = 0;
var januaryLogs = logs.Where(x => x.ClockInTimeStamp >= startDate &&
x.ClockOutTimeStamp <= endDate);
foreach(var item in januaryLogs)
{
totalTimeWorkedInMinutes += GetTimeDifferenceInMinutes(item.ClockInTimeStamp, itemClockOutTimeStamp);
}
return totalTimeWorkedInMinutes;
}
var logsFilteredByDate = logs.Where(x => x.ClockInTimeStamp >= startDate &&
x.ClockOutTimeStamp <= endDate);
var totalTimeWorkedInMinutes = logsFilteredByDate.Sum(x =>
GetTimeDifferenceInMinutes(x.ClockInTimeStamp, x.ClockOutTimeStamp));
Or, to combine it all into one query, which is unnecessary and harder to read,
var totalTimeWorkedInMinutes = logs.Where(x => x.ClockInTimeStamp >= startDate &&
x.ClockOutTimeStamp <= endDate)
.Sum(x =>
GetTimeDifferenceInMinutes(x.ClockInTimeStamp, x.ClockOutTimeStamp));
you need sum
var tot = januaryLogs.Sum(item=>GetTimeDifferenceInMinutes(item.ClockInTimeStamp, itemClockOutTimeStamp));
Couldn't you do the Where with a Sum and do DateTime Subtract in the Sum, so
decimal total = logs.Where(x => x.ClockInTimeStamp >= startDate && x.ClockOutTimeStamp <= endDate).Sum(x.ClockOutTimeStamp.Subtract(x.ClockInTimeStamp).TotalMinutes);
The problem seems easy until you realize that a time sheet can span months. So if someone clocked in on January 31st and clocked out on February 1st, you have to count partial timesheets, to do it right.
Here is my solution:
public static class ExtensionMethods
{
static public double TotalMinutes(this IEnumerable<TimeSheetLog> input, DateTime startPeriod, DateTime endPeriod)
{
return TimeSpan.FromTicks
(
input
.Where( a=>
a.ClockOutTimeStamp >= startPeriod &&
a.ClockInTimeStamp <= endPeriod
)
.Select( a=>
Math.Min(a.ClockOutTimeStamp.Ticks, endPeriod.Ticks) -
Math.Max(a.ClockInTimeStamp.Ticks, startPeriod.Ticks)
)
.Sum()
)
.TotalMinutes;
}
}
Logic:
Find all timesheets that overlap at least partially with the period of interest.
Compute the start time as either the clock in time or the period start time, whichever is later.
Compute the end time as either the clock out time or the period end time, whichever is earlier.
Take the difference of the start and end time as ticks. Sum() these.
To do all this math, we convert all the timestamps to Ticks, since you can't take a Max() of two DateTimes. We can add ticks up just fine, then convert the total back into minutes before returning.
Test program (notice the third timesheet spans both January and February):
public class Program
{
static public List<TimeSheetLog> testData = new List<TimeSheetLog>
{
new TimeSheetLog
{
ClockInTimeStamp = DateTime.Parse("1/1/2018 9:00 am"),
ClockOutTimeStamp = DateTime.Parse("1/1/2018 5:00 pm")
},
new TimeSheetLog
{
ClockInTimeStamp = DateTime.Parse("1/2/2018 9:00 am"),
ClockOutTimeStamp = DateTime.Parse("1/2/2018 5:00 pm")
},
new TimeSheetLog
{
ClockInTimeStamp = DateTime.Parse("1/31/2018 6:00 pm"),
ClockOutTimeStamp = DateTime.Parse("2/1/2018 9:00 am")
},
new TimeSheetLog
{
ClockInTimeStamp = DateTime.Parse("2/3/2018 9:00 am"),
ClockOutTimeStamp = DateTime.Parse("2/3/2018 5:00 pm")
}
};
public static void Main()
{
var startPeriod = new DateTime(2018, 1, 1);
var endPeriod = new DateTime(2018, 1, 31, 23, 59, 59, 9999);
Console.WriteLine( testData.TotalMinutes(startPeriod, endPeriod).ToString("0.00") );
}
}
Output:
1320.00
...which is correct.
See my code on DotNetFiddle
Another option is to use .Aggregate function.
public static int GetTotalTimeWorked(List<TimeSheetLog> logs, DateTime startDate, DateTime endDate)
{
var totalTimeWorkedInMinutes = 0;
return logs.Where(x => x.ClockInTimeStamp >= startDate && x.ClockOutTimeStamp <= endDate)
.Aggregate(totalTimeWorkedInMinutes, (total, item) => total + GetTimeDifferenceInMinutes(item.ClockInTimeStamp, item.ClockOutTimeStamp));
}

Is there an optimized way of getting business weeks between two dates?

Just wondering if there is a more optimized and/or neater way (using LINQ for example) of writing what I have below to get a list of business week date ranges between two dates?
This is what I have currently ..
// Some storage
public class Bucket
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
// Other code removed for brevity ...
DateTime start = new DateTime(2015, 7, 1);
DateTime end = new DateTime(2015, 9, 1);
DayOfWeek firstDayOfWeek = DayOfWeek.Monday;
DayOfWeek lastDayOfWeek = DayOfWeek.Friday;
var buckets = new List<Bucket>();
var currentDate = start;
DateTime startOfBucket = currentDate;
DateTime endOfBucket = currentDate;
while (currentDate <= end)
{
var currentDayOfWeek = currentDate.DayOfWeek;
// Skip days outside the business week
if (currentDayOfWeek >= firstDayOfWeek && currentDayOfWeek <= lastDayOfWeek)
{
if (currentDayOfWeek == firstDayOfWeek)
{
// Start a new bucket
startOfBucket = currentDate;
}
if ((currentDayOfWeek == lastDayOfWeek) || (currentDate == end))
{
// End of bucket
endOfBucket = currentDate;
// Create bucket
buckets.Add(new Bucket()
{
StartDate = startOfBucket,
EndDate = endOfBucket
});
}
}
currentDate = currentDate.AddDays(1);
}
And this will give me the following date ranges ...
Start: 01/Jul/2015 End: 03/Jul/2015
Start: 06/Jul/2015 End: 10/Jul/2015
Start: 13/Jul/2015 End: 17/Jul/2015
Start: 20/Jul/2015 End: 24/Jul/2015
Start: 27/Jul/2015 End: 31/Jul/2015
Start: 03/Aug/2015 End: 07/Aug/2015
Start: 10/Aug/2015 End: 14/Aug/2015
Start: 17/Aug/2015 End: 21/Aug/2015
Start: 24/Aug/2015 End: 28/Aug/2015
Start: 31/Aug/2015 End: 01/Sep/2015
N.B. The first and last weeks are purposefully not full weeks (they abide to the date range given).
Edit
The solution provided here gives the number of days between the two dates but I am interested in getting the collection of date ranges.
Also, I don't need to account for any holidays.
Thanks,
It's quite handy using linq
var startDate = new DateTime(2015, 7, 1);
var endDate = new DateTime(2015, 9, 1);
var workDates = Enumerable.Range(0, (int)(endDate - startDate).TotalDays + 1)
.Select(i => startDate.AddDays(i))
.Where(date => (date.DayOfWeek != DayOfWeek.Saturday && date.DayOfWeek != DayOfWeek.Sunday))
.Select(i => i);
var display = workDates
.GroupAdjacentBy((x, y) => x.AddDays(1) == y)
.Select(g => string.Format("Start: {0:dd/MMM/yyyy} End: {1:dd/MMM/yyyy}", g.First(), g.Last()));
With the extension method GroupAdjacentBy<T>
public static class IEnumerableExtension
{
public static IEnumerable<IEnumerable<T>> GroupAdjacentBy<T>(
this IEnumerable<T> source, Func<T, T, bool> predicate)
{
using (var e = source.GetEnumerator())
{
if (e.MoveNext())
{
var list = new List<T> { e.Current };
var pred = e.Current;
while (e.MoveNext())
{
if (predicate(pred, e.Current))
{
list.Add(e.Current);
}
else
{
yield return list;
list = new List<T> { e.Current };
}
pred = e.Current;
}
yield return list;
}
}
}
}
Fiddle
This is based on Eric's accepted answer so please give him any upvote. I've just modified his solution to handle business weeks that could be 7 days long and also for one that could wrap a weekend.
var startDate = new DateTime(2015, 7, 1);
var endDate = new DateTime(2015, 9, 1);
DayOfWeek firstDayOfWeek = DayOfWeek.Monday;
DayOfWeek lastDayOfWeek = DayOfWeek.Friday;
var workDates = Enumerable.Range(0, (int)(endDate - startDate).TotalDays + 1)
.Select(i => startDate.AddDays(i))
.Where(date =>
// Normal work weeks where first day of week is before last (numerically) e.g. Monday -> Friday or Sunday -> Saturday
(firstDayOfWeek < lastDayOfWeek && date.DayOfWeek >= firstDayOfWeek && date.DayOfWeek <= lastDayOfWeek) ||
// Cater for business weeks whose start and end dates wrap over the weekend e.g. Thursday -> Tuesday
(lastDayOfWeek < firstDayOfWeek && (date.DayOfWeek >= firstDayOfWeek || date.DayOfWeek <= lastDayOfWeek)))
.Select(i => i);
var display = workDates
.GroupAdjacentBy((x, y) => x.AddDays(1) == y && !(x.DayOfWeek == lastDayOfWeek && y.DayOfWeek == firstDayOfWeek))
.Select(g => string.Format("Start: {0:dd/MMM/yyyy} End: {1:dd/MMM/yyyy}", g.First(), g.Last()));

How do I loop through a date range?

I'm not even sure how to do this without using some horrible for loop/counter type solution. Here's the problem:
I'm given two dates, a start date and an end date and on a specified interval I need to take some action. For example: for every date between 3/10/2009 on every third day until 3/26/2009 I need to create an entry in a List. So my inputs would be:
DateTime StartDate = "3/10/2009";
DateTime EndDate = "3/26/2009";
int DayInterval = 3;
and my output would be a list that has the following dates:
3/13/2009
3/16/2009
3/19/2009
3/22/2009
3/25/2009
So how the heck would I do something like this? I thought about using a for loop that would iterate between every day in the range with a separate counter like so:
int count = 0;
for(int i = 0; i < n; i++)
{
count++;
if(count >= DayInterval)
{
//take action
count = 0;
}
}
But it seems like there could be a better way?
Well, you'll need to loop over them one way or the other. I prefer defining a method like this:
public IEnumerable<DateTime> EachDay(DateTime from, DateTime thru)
{
for(var day = from.Date; day.Date <= thru.Date; day = day.AddDays(1))
yield return day;
}
Then you can use it like this:
foreach (DateTime day in EachDay(StartDate, EndDate))
// print it or whatever
In this manner you could hit every other day, every third day, only weekdays, etc. For example, to return every third day starting with the "start" date, you could just call AddDays(3) in the loop instead of AddDays(1).
I have a Range class in MiscUtil which you could find useful. Combined with the various extension methods, you could do:
foreach (DateTime date in StartDate.To(EndDate).ExcludeEnd()
.Step(DayInterval.Days())
{
// Do something with the date
}
(You may or may not want to exclude the end - I just thought I'd provide it as an example.)
This is basically a ready-rolled (and more general-purpose) form of mquander's solution.
For your example you can try
DateTime StartDate = new DateTime(2009, 3, 10);
DateTime EndDate = new DateTime(2009, 3, 26);
int DayInterval = 3;
List<DateTime> dateList = new List<DateTime>();
while (StartDate.AddDays(DayInterval) <= EndDate)
{
StartDate = StartDate.AddDays(DayInterval);
dateList.Add(StartDate);
}
Code from #mquander and #Yogurt The Wise used in extensions:
public static IEnumerable<DateTime> EachDay(DateTime from, DateTime thru)
{
for (var day = from.Date; day.Date <= thru.Date; day = day.AddDays(1))
yield return day;
}
public static IEnumerable<DateTime> EachMonth(DateTime from, DateTime thru)
{
for (var month = from.Date; month.Date <= thru.Date || month.Month == thru.Month; month = month.AddMonths(1))
yield return month;
}
public static IEnumerable<DateTime> EachDayTo(this DateTime dateFrom, DateTime dateTo)
{
return EachDay(dateFrom, dateTo);
}
public static IEnumerable<DateTime> EachMonthTo(this DateTime dateFrom, DateTime dateTo)
{
return EachMonth(dateFrom, dateTo);
}
1 Year later, may it help someone,
This version includes a predicate, to be more flexible.
Usage
var today = DateTime.UtcNow;
var birthday = new DateTime(2018, 01, 01);
Daily to my birthday
var toBirthday = today.RangeTo(birthday);
Monthly to my birthday, Step 2 months
var toBirthday = today.RangeTo(birthday, x => x.AddMonths(2));
Yearly to my birthday
var toBirthday = today.RangeTo(birthday, x => x.AddYears(1));
Use RangeFrom instead
// same result
var fromToday = birthday.RangeFrom(today);
var toBirthday = today.RangeTo(birthday);
Implementation
public static class DateTimeExtensions
{
public static IEnumerable<DateTime> RangeTo(this DateTime from, DateTime to, Func<DateTime, DateTime> step = null)
{
if (step == null)
{
step = x => x.AddDays(1);
}
while (from < to)
{
yield return from;
from = step(from);
}
}
public static IEnumerable<DateTime> RangeFrom(this DateTime to, DateTime from, Func<DateTime, DateTime> step = null)
{
return from.RangeTo(to, step);
}
}
Extras
You could throw an Exception if the fromDate > toDate, but I prefer to return an empty range instead []
DateTime startDate = new DateTime(2009, 3, 10);
DateTime stopDate = new DateTime(2009, 3, 26);
int interval = 3;
for (DateTime dateTime=startDate;
dateTime < stopDate;
dateTime += TimeSpan.FromDays(interval))
{
}
DateTime begindate = Convert.ToDateTime("01/Jan/2018");
DateTime enddate = Convert.ToDateTime("12 Feb 2018");
while (begindate < enddate)
{
begindate= begindate.AddDays(1);
Console.WriteLine(begindate + " " + enddate);
}
According to the problem you can try this...
// looping between date range
while (startDate <= endDate)
{
//here will be your code block...
startDate = startDate.AddDays(1);
}
thanks......
DateTime startDate = new DateTime(2009, 3, 10);
DateTime stopDate = new DateTime(2009, 3, 26);
int interval = 3;
while ((startDate = startDate.AddDays(interval)) <= stopDate)
{
// do your thing
}
Here are my 2 cents in 2020.
Enumerable.Range(0, (endDate - startDate).Days + 1)
.ToList()
.Select(a => startDate.AddDays(a));
You can use the DateTime.AddDays() function to add your DayInterval to the StartDate and check to make sure it is less than the EndDate.
You might consider writing an iterator instead, which allows you to use normal 'for' loop syntax like '++'. I searched and found a similar question answered here on StackOverflow which gives pointers on making DateTime iterable.
you have to be careful here not to miss the dates when in the loop a better solution would be.
this gives you the first date of startdate and use it in the loop before incrementing it and it will process all the dates including the last date of enddate hence <= enddate.
so the above answer is the correct one.
while (startdate <= enddate)
{
// do something with the startdate
startdate = startdate.adddays(interval);
}
you can use this.
DateTime dt0 = new DateTime(2009, 3, 10);
DateTime dt1 = new DateTime(2009, 3, 26);
for (; dt0.Date <= dt1.Date; dt0=dt0.AddDays(3))
{
//Console.WriteLine(dt0.Date.ToString("yyyy-MM-dd"));
//take action
}
Iterate every 15 minutes
DateTime startDate = DateTime.Parse("2018-06-24 06:00");
DateTime endDate = DateTime.Parse("2018-06-24 11:45");
while (startDate.AddMinutes(15) <= endDate)
{
Console.WriteLine(startDate.ToString("yyyy-MM-dd HH:mm"));
startDate = startDate.AddMinutes(15);
}
#jacob-sobus and #mquander and #Yogurt not exactly correct.. If I need the next day I wait 00:00 time mostly
public static IEnumerable<DateTime> EachDay(DateTime from, DateTime thru)
{
for (var day = from.Date; day.Date <= thru.Date; day = day.NextDay())
yield return day;
}
public static IEnumerable<DateTime> EachMonth(DateTime from, DateTime thru)
{
for (var month = from.Date; month.Date <= thru.Date || month.Year == thru.Year && month.Month == thru.Month; month = month.NextMonth())
yield return month;
}
public static IEnumerable<DateTime> EachYear(DateTime from, DateTime thru)
{
for (var year = from.Date; year.Date <= thru.Date || year.Year == thru.Year; year = year.NextYear())
yield return year;
}
public static DateTime NextDay(this DateTime date)
{
return date.AddTicks(TimeSpan.TicksPerDay - date.TimeOfDay.Ticks);
}
public static DateTime NextMonth(this DateTime date)
{
return date.AddTicks(TimeSpan.TicksPerDay * DateTime.DaysInMonth(date.Year, date.Month) - (date.TimeOfDay.Ticks + TimeSpan.TicksPerDay * (date.Day - 1)));
}
public static DateTime NextYear(this DateTime date)
{
var yearTicks = (new DateTime(date.Year + 1, 1, 1) - new DateTime(date.Year, 1, 1)).Ticks;
var ticks = (date - new DateTime(date.Year, 1, 1)).Ticks;
return date.AddTicks(yearTicks - ticks);
}
public static IEnumerable<DateTime> EachDayTo(this DateTime dateFrom, DateTime dateTo)
{
return EachDay(dateFrom, dateTo);
}
public static IEnumerable<DateTime> EachMonthTo(this DateTime dateFrom, DateTime dateTo)
{
return EachMonth(dateFrom, dateTo);
}
public static IEnumerable<DateTime> EachYearTo(this DateTime dateFrom, DateTime dateTo)
{
return EachYear(dateFrom, dateTo);
}
If you convert your dates to OADate you can loop thru them as you would do with any double number.
DateTime startDate = new DateTime(2022, 1, 1);
DateTime endDate = new DateTime(2022, 12, 31);
for (double loopDate = startDate.ToOADate(); loopDate <= endDate.ToOADate(); loopDate++)
{
DateTime selectedDate;
selectedDate = DateTime.FromOADate(loopDate);
}

Categories

Resources