How to select weekends with an interval between them? - c#

How can I select all weekends until the end of the year, with some criteria to be followed?
User input desired Weekend day:
18/12/2021
Software must out:
25/12/2022 (must be ignored)
01/01/2022
08/01/2022 (must be ignored)
15/01/2022
22/01/2022 (must be ignored)
29/01/2022 and so on...
What i have now:
public void GetWeekends() {
var lastWorkedWeekend = dateTimePicker1.Value;
var workedInSunday = checkBox1.Checked;
var list = new List < DateTime > ();
var weekends = GetDaysBetween(lastWorkedWeekend, DateTime.Today.AddDays(365)).Where(d => d.DayOfWeek == DayOfWeek.Saturday);
var selected = true;
for (int i = 0; i < weekends.Count(); i++) {
if (selected == false) {
list.Add(weekends.ElementAt(i));
selected = true;
} else {
selected = false;
}
}
}

I think I'd just scroll the input day forward until it was saturday (or calculate it, but i find the loop more self documenting than casting DayOfWeek to an int and factoring for sunday being 0) then add 14 days repeatedly. This skips over the 25th, etc..
var d = new DateTime(2021, 12, 18);
while(d.DayOfWeek != DayOfWeek.Saturday)
d += TimeSpan.FromDays(1);
while(d.Year == 2021){ //was your 2022 a typo? or maybe make this <= 2022.. I'm not sure what you want there..
d += TimeSpan.FromDays(14);
Console.WriteLine(d);
}
You might prefer DateTime.AddDays()..

Looks to me that you want to get the date of every second weekend from an initial date until the end of the year. In your example, you kinda bleed over to the next year.
public static void Main()
{
var dates = GetDateTimeRange(new DateTime(2021, 12, 18), new DateTime(2023, 1, 1), TimeSpan.FromDays(14));
foreach (var dateTime in dates.Skip(1))
{
Console.WriteLine(dateTime);
}
}
public static IEnumerable<DateTime> GetDateTimeRange(DateTime startingDate, DateTime endDate, TimeSpan interval)
{
var lastDate = startingDate;
while (lastDate < endDate)
{
yield return lastDate;
lastDate = lastDate.Add(interval);
}
}
This returns
01/01/2022 00:00:00
01/15/2022 00:00:00
01/29/2022 00:00:00
02/12/2022 00:00:00
02/26/2022 00:00:00
03/12/2022 00:00:00
03/26/2022 00:00:00
04/09/2022 00:00:00
04/23/2022 00:00:00
05/07/2022 00:00:00
05/21/2022 00:00:00
06/04/2022 00:00:00
06/18/2022 00:00:00
07/02/2022 00:00:00
07/16/2022 00:00:00
07/30/2022 00:00:00
08/13/2022 00:00:00
08/27/2022 00:00:00
09/10/2022 00:00:00
09/24/2022 00:00:00
10/08/2022 00:00:00
10/22/2022 00:00:00
11/05/2022 00:00:00
11/19/2022 00:00:00
12/03/2022 00:00:00
12/17/2022 00:00:00
12/31/2022 00:00:00

One approach is to use a method that creates an enumerable of dates. Once that's done you can use LINQ queries. If creating that enumerable is expensive you could just create one large range encompassing years and re-use it.
Or you might find better performance by just creating the range you need for each query.
public static class DateRanges
{
public static IEnumerable<DateOnly> GetRange(DateOnly start, DateOnly end)
{
for (var date = start; date <= end; date = date.AddDays(1))
{
yield return date;
}
}
}
It's not clear from your code what the criteria is, but this does what you described - all weekends from now to the end of the year.
var today = DateOnly.FromDateTime(DateTime.Today);
var lastDayOfYear = DateOnly.FromDateTime(new DateTime(DateTime.Today.Year, 12, 31));
var dates = DateRanges.GetRange(today, lastDayOfYear);
var weekendsOnly = dates.Where(date =>
date.DayOfWeek == DayOfWeek.Saturday);
Or if you prefer to create one date range and query it repeatedly:
// Big range of dates, 10 years into past and future.
// Create this once and re-use it
var dates = DateRanges.GetRange(
DateOnly.FromDateTime(DateTime.Today.AddYears(-10)),
DateOnly.FromDateTime(DateTime.Today.AddYears(10)));
var today = DateOnly.FromDateTime(DateTime.Today);
var lastDayOfYear = DateOnly.FromDateTime(new DateTime(DateTime.Today.Year, 12, 31));
var weekendsOnly = dates.Where(date =>
date >= today
&& date <= lastDayOfYear
&& date.DayOfWeek == DayOfWeek.Saturday);
In either case if you want every other Saturday you can add
.Where((date, i) => i % 2 == 0);
Or to maintain that readability you could put that in another extension like
public static IEnumerable<T> EveryOther<T>(this IEnumerable<T> source)
{
return source.Where((date, i) => i % 2 == 0);
}
so your query looks like
var everyOtherWeekend = dates
.Where(date =>
date >= today
&& date <= lastDayOfYear
&& date.DayOfWeek == DayOfWeek.Saturday)
.EveryOther();
I wouldn't position this as a better answer than those that iterate over a series of dates. The difference is that instead of having to write a method for any query you can start with a range of dates and then use LINQ to filter it. That makes it a little easier to read and to compose different queries.

Related

Current week Monday is printed

How can I set to C# property that day is automatically printed as Monday of current week?
public DateTime MondayOfCurrentWeek(this DateTime dt)
{
var today = DateTime.Now;
return new GregorianCalendar().AddDays(today, -((int)today.DayOfWeek) + 1);
}
Console.WriteLine($"Document date - {Document.MondayOfCurrentWeek(DateTime.Now)}");
You can use DayOfWeek property of DateTime. Following function would work:
DateTime MondayOfCurrentWeek()
{
int DaysToMonday(DayOfWeek dayOfWeek) => dayOfWeek switch
{
DayOfWeek.Monday => 0,
DayOfWeek.Tuesday => -1,
DayOfWeek.Wednesday => -2,
DayOfWeek.Thursday => -3,
DayOfWeek.Friday => -4,
DayOfWeek.Saturday => -5,
DayOfWeek.Sunday => -6
};
var now = DateTime.Now;
var mondayOfCurrentWeek = now.AddDays(DaysToMonday(now.DayOfWeek));
return mondayOfCurrentWeek;
}
The problem you see is that the default "start of the week" is Sunday. I.e. DayOfWeek.Sunday is 0 while DayOfWeek.Monday is 1. So in your case, when var today = DateTime.Now is a Sunday, today.DayOfWeek will return 0;
If you want a less hard-coded way to get the date for the start of the week, you can do something like this:
public DateTime StartOfWeek(
DateTime dt,
DayOfWeek weekStart = DayOfWeek.Monday)
{
const int totalWeekDays = 7;
var daysSinceWeekStart = (totalWeekDays - ((int)weekStart - (int)dt.DayOfWeek)) % totalWeekDays;
return dt.AddDays(-daysSinceWeekStart);
}
// Use it like this:
Console.WriteLine($"Document date - {Document.StartOfWeek(DateTime.Now)}");
// Or, if you want Sunday to be the start of the week:
Console.WriteLine($"Document date - {Document.StartOfWeek(DateTime.Now, DayOfWeek.Sunday)}");

Calculate Date Ranges in Chunks by Year

Writing a small application to calculate interest but the rate changes yearly. Needed to break the range into smaller date ranges when ever it crosses a year boundary. I wrote a little for loop to do it but it's rather clunky. Wondering if there are any built in functions to do this in C# (possible linq). Would basically be looking to return a list of date ranges with the corresponding base year (shortened code for readability).
static void Main(string[] args)
{
var dateStart = DateTime.Parse("2/10/2018");
var dateEnd = DateTime.Parse("3/10/2021");
var years = Years(dateStart, dateEnd);
var baseYear = dateStart.Year;
Console.WriteLine(baseYear);
var loopDateStart = dateStart;
var loopDateEnd = DateTime.Now;
for (int i = 0; i < years + 1; i++)
{
if (i < years) {
loopDateEnd = DateTime.Parse("1/1/" + (baseYear + 1));
Console.WriteLine(loopDateEnd + " ... " + loopDateStart);
Console.WriteLine((loopDateEnd - loopDateStart).Days);
loopDateStart = loopDateEnd;
baseYear++;
}
else {
loopDateEnd = dateEnd;
Console.WriteLine(loopDateEnd + " ... " + loopDateStart);
Console.WriteLine((loopDateEnd - loopDateStart).Days);
}
}
}
public static int Years(DateTime start, DateTime end)
{
return (end.Year - start.Year - 1) +
(((end.Month > start.Month) ||
((end.Month == start.Month) && (end.Day >= start.Day))) ? 1 : 0);
}
Sure, we can use LINQ:
var x = Enumerable.Range(dateStart.Year, (dateEnd.Year-dateStart.Year)+1)
.Select(y => new{
F = new[]{dateStart, new DateTime(y,1,1)}.Max(),
T = new[]{dateEnd, new DateTime(y,12,31)}.Min()
});
It generates an enumerable list of objects that have an F and a T property (from and to) that are your ranges.
It works by using Enumerable.Range to make a list of years: 2018,2019,2020,2021 by starting at 2108 and proceeding for 4 years (2018 to 2018 is one year entry, 2018 to 2021 is 4 year entries)
Then we just turn them into dates using new DateTime(year,amonth,aday) - when were making start dates, amonth and aday are 1 and 1, when making end dates they're 12 and 31
Then we just ask for every year y, "which date is greater, the startdate, or the 1-Jan-y" and "which date is lesser, the enddate or the 31-Dec-y " - for the initial and final date entry it's the startdate and the enddate that are greater and lesser. For other years it's the jan/dec dates. This gives the ranges you want
foreach(var xx in x){
Console.WriteLine(xx.F +" to "+xx.T);
}
2/10/2018 12:00:00 AM to 12/31/2018 12:00:00 AM
1/1/2019 12:00:00 AM to 12/31/2019 12:00:00 AM
1/1/2020 12:00:00 AM to 12/31/2020 12:00:00 AM
1/1/2021 12:00:00 AM to 3/10/2021 12:00:00 AM
If you want to do other work like the number of days between, you can do xx.T-xx.F in the loop, to make a timespan etc
Try:
var start = DateTime.Parse("4/5/2017");
var end = DateTime.Parse("3/1/2019");
DateTime chunkEnd;
for (var chunkStart = start; chunkStart < end; chunkStart = chunkEnd.AddDays(1))
{
var lastDay = new DateTime(chunkStart.Year, 12, 31);
chunkEnd = end > lastDay ? lastDay : end;
var days = (chunkEnd - chunkStart).Days;
Console.WriteLine($"{chunkStart:d} - {chunkEnd:d}; {days} days");
}
Produces:
4/5/2017 - 12/31/2017; 270 days
1/1/2018 - 12/31/2018; 364 days
1/1/2019 - 3/1/2019; 59 days
I came up with the following:
static IEnumerable<(DateTime,DateTime)> ChunkByYear(DateTime start, DateTime end)
{
// Splits <start,end> into chunks each belonging to a different year
while(start <= end)
{
var tempEnd = new DateTime(start.Year, 12, 31);
if(tempEnd >= end ) {
yield return (start, end);
yield break;
}
yield return (start, tempEnd);
start = tempEnd.AddDays(1);
}
}
Here are some results:
4/05/2017 to 3/01/2019:
4/05/2017->31/12/2017
1/01/2018->31/12/2018
1/01/2019->3/01/2019
4/05/2017 to 4/05/2017:
4/05/2017->4/05/2017
31/12/2017 to 31/12/2019:
31/12/2017->31/12/2017
1/01/2018->31/12/2018
1/01/2019->31/12/2019
31/12/2019 to 31/12/2019:
31/12/2019->31/12/2019
31/12/2018 to 1/01/2019:
31/12/2018->31/12/2018
1/01/2019->1/01/2019
Group by years:
using System.Collections.Generic;
using System.Linq;
...
static void Main(string[] args)
{
DateTime dateStart = DateTime.Parse("2/10/2018");
DateTime dateEnd = DateTime.Parse("3/10/2021");
// Group all possible dates by year
foreach(var group in GetDates(dateStart, dateEnd).GroupBy(date => date.Year))
{
Console.WriteLine(group.Key); // The key of the group is year
Console.WriteLine($"{group.Min()} ... {group.Max()}"); // Range: From minimum to maximum, order doesn't matter.
Console.WriteLine($"{group.First()} ... {group.Last()}"); //or Range version 2: From first to last, order matters.
Console.WriteLine(group.Count()); // Count days
}
}
/// <summary>
/// Get all days blindly, might need to pay attention to days on the boundaries
/// </summary>
private static IEnumerable<DateTime> GetDates(DateTime start, DateTime end)
{
// TODO: Check start <= end;
DateTime current = start;
while(current <= end)
{
yield return current;
current = current.AddDays(1);
}
}

How to return a list of weekend dates between 2 dates

At the moment I have this code to return a table of all dates between 2 dates. How could I change this to have it only return the weekend dates.
The purpose of this is to use the weekend dates to check against column headers in a DataGridView to "grey-out" the weekends. I hope that's clear.
static public List<string> GetDates(DateTime start_date, DateTime end_date)
{
List<string> days_list = new List<string>();
for (DateTime date = start_date; date <= end_date; date = date.AddDays(1))
{
days_list.Add(date.ToShortDateString());
}
return days_list;
}
Use the DateTime.DayOfWeek property.
https://msdn.microsoft.com/en-US/library/system.datetime.dayofweek(v=vs.110).aspx
static public List<string> GetDates(DateTime start_date, DateTime end_date)
{
List<string> days_list = new List<string>();
for (DateTime date = start_date; date <= end_date; date = date.AddDays(1))
{
if (date.DayOfWeek == DayOfWeek.Sunday || date.DayOfWeek == DayOfWeek.Saturday)
days_list.Add(date.ToShortDateString());
}
return days_list;
You can create range of dates and then filter on them using DayOfWeek as #Vitor said:
static public List<DateTime> GetWeekendDates(DateTime start_date, DateTime end_date)
{
return Enumerable.Range(0, (int)((end_date- start_date).TotalDays) + 1)
.Select(n => StartDate.AddDays(n))
.Where(x=>x.DayOfWeek == DayOfWeek.Saturday
|| x.DayOfWeek == DayOfWeek.Sunday)
.ToList();
}
hope this solution will help you
DateTime startDate = new DateTime(2011,3,1);
DateTime endDate = DateTime.Now;
TimeSpan diff = endDate - startDate;
int days = diff.Days;
for (var i = 0; i <= days; i++)
{
var testDate = startDate.AddDays(i);
switch (testDate.DayOfWeek)
{
case DayOfWeek.Saturday:
case DayOfWeek.Sunday:
Console.WriteLine(testDate.ToShortDateString());
break;
}
}
in above code I am finding Saturday and Sunday between 1st March 2011 and today. So I have taken two variables called startDate and endDate. After that I have got difference between them and then via for loop I am checking that day of week is Saturday or Sunday

How do I determine if a date lies between current week dates?

In C#,
How we are check certain date with in week dates?
Eg: 6/02/2014
Current Weeks: 02/02/2014 - 08/02/2014
so this dates are with in above week....
Use this for check (last parameter is optional if you want always 1 week from fromDate, you don't need use last parameter):
public static bool DateInside(DateTime checkDate,
DateTime fromDate, DateTime? lastDate = null)
{
DateTime toDate = lastDate != null ? lastDate.Value : fromDate.AddDays(6d);
return checkDate >= fromDate && checkDate <= toDate;
}
To call use:
bool isDateInside = DateInside(new DateTime(2014, 02, 06),
new DateTime(2014, 02, 02)); // return true
And search first :) Answer is also here: How to check whether C# DateTime is within a range
If you want to check if the dates are inside the same week, then you can use this:
public static bool DateInsideOneWeek(DateTime checkDate, DateTime referenceDate)
{
// get first day of week from your actual culture info,
DayOfWeek firstWeekDay = System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek;
// or you can set exactly what you want: firstWeekDay = DayOfWeek.Monday;
// calculate first day of week from your reference date
DateTime startDateOfWeek = referenceDate;
while(startDateOfWeek.DayOfWeek != firstWeekDay)
{ startDateOfWeek = startDateOfWeek.AddDays(-1d); }
// fist day of week is find, then find last day of reference week
DateTime endDateOfWeek = startDateOfWeek.AddDays(6d);
// and check if checkDate is inside this period
return checkDate >= startDateOfWeek && checkDate <= endDateOfWeek;
}
Actual week in my culture info start with monday, February 3th 2014 (so for me is week between February 3th and February 9th). If I check any date with reference date (second parameter) as today (2014-Feb-06) I get this results:
For 2014-Feb-02 (Sunday before this week): false
For 2014-Feb-03 (Monday inside this week): true
For 2014-Feb-06 (Today inside this week): true
For 2014-Feb-09 (Sunday inside this week): true
For 2014-Feb-10 (Monday next week): false
You can call this method to check if one date is inside the same week as referentional like this:
DateInsideOneWeek(new DateTime(2014, 02, 02), new DateTime(2014, 02, 06));
You can find current week start and end dates with this code:
DateTime startDateOfWeek = DateTime.Now.Date; // start with actual date
while(startDateOfWeek.DayOfWeek != DayOfWeek.Monday) // set first day of week in your country
{ startDateOfWeek = startDateOfWeek.AddDays(-1d); } // after this while loop you get first day of actual week
DateTime endDateOfWeek = startDateOfWeek.AddDays(6d); // you just find last week day
Is this you wanted?
hmm
public bool isBetween(DateTime input, DateTime date1, DateTime date2)
{
if (input > date1 && input < date2)
return true;
else
return false;
}
?
input= your date
date1 & date2 = start and end of a week
How about:
bool inRange = (date >= lowerDate && date <= upperDate);
Here's another solution :)
public static class DateExtensions
{
private static void Swap<T>(ref T one, ref T two)
{
var temp = one;
one = two;
two = temp;
}
public static bool IsFromSameWeek(this DateTime first, DateTime second, DayOfWeek firstDayOfWeek = DayOfWeek.Monday)
{
// sort dates
if (first > second)
{
Swap(ref first, ref second);
}
var daysDiff = (second - first).TotalDays;
if (daysDiff >= 7)
{
return false;
}
const int TotalDaysInWeek = 7;
var adjustedDayOfWeekFirst = (int)first.DayOfWeek + (first.DayOfWeek < firstDayOfWeek ? TotalDaysInWeek : 0);
var adjustedDayOfWeekSecond = (int)second.DayOfWeek + (second.DayOfWeek < firstDayOfWeek ? TotalDaysInWeek : 0);
return adjustedDayOfWeekSecond >= adjustedDayOfWeekFirst;
}
}
Upd: it appears to have at least twice better performance than #Atiris solution :)

Get a List of Dates In a Date Range

Say I have a list of LockedDate.
A LockedDate has a DateTime and an IsYearly bool. If IsYearly is true then the year should not be considered because it could be any year. Time should never be considered.
Ex: X-Mas, Dec 25 is yearly.
Now I have a List of LockedDate.
There are no duplicates.
Now I need this function:
This function will do:
If a LockedDate is NOT yearly and the day, month, and year are within the range from source, add to return list.
If a LockedDate IS yearly, and its month / day fall in the range, then add a new date for each year in the range.
Say I have Dec 25 with IsYearly as true. My range is Jan 22 2013 to Feb 23 2015 inclusive. would need to add Dec 25 2013 as a new date and Dec 25 2014 as a new Date to the list.
List<Date> GetDateRange(List<LockedDate> source, DateTime start, DateTime end)
{
}
Thanks
Dec 25 Yearly -> Dec 25 2013, Dec 25 2014
Dec 2, 2011 NOT Yearly -> Nothing
March 25, 2013 => March 25 2013
This might give you at least an idea, it's not tested at all yet:
List<DateTime> GetDateRange(List<LockedDate> source, DateTime start, DateTime end)
{
if (start > end)
throw new ArgumentException("Start must be before end");
var ts = end - start;
var dates = Enumerable.Range(0, ts.Days)
.Select(i => start.AddDays(i))
.Where(d => source.Any(ld => ld.Date == d
|| (ld.IsYearly && ld.Date.Month == d.Month && ld.Date.Day == d.Day)));
return dates.ToList();
}
Update Here's the demo with your sample data, it seems to work: http://ideone.com/3KFi97
This code does what you want without using Linq:
List<DateTime> GetDateRange(List<LockedDate> source, DateTime start, DateTime end)
{
List<DateTime> result = new List<DateTime>();
foreach (LockedDate lockedDate in source)
{
if (!lockedDate.IsYearly && (lockedDate.Date >= start && lockedDate.Date <= end))
{
result.Add(lockedDate.Date);
}
else if (lockedDate.IsYearly && (lockedDate.Date >= start && lockedDate.Date <= end))
{
DateTime date = new DateTime(start.Year, lockedDate.Date.Month, lockedDate.Date.Day);
do
{
result.Add(date);
date = date.AddYears(1);
} while (date <= end);
}
}
return result;
}
Make sure to ask about parts you don't understand, so I can explain in detail, but I think it's pretty straightforward.
This code assumes your LockedDate class has a property Date for the DateTime object.
var notYearly = lockDates.Where(d => !d.IsYearly && (d.Date.Date >= start && d.Date.Date <= end)).Select(d => d.Date);
var years = ((end - start).Days / 365) + 2;
var startYear = start.Year;
var yearly = lockDates.Where(d => d.IsYearly)
.SelectMany(d => Enumerable.Range(startYear, years)
.Select(e => new DateTime(e, d.Date.Month, d.Date.Day))
.Where(i => i.Date >= start && i.Date <= end));
var allDates = notYearly.Union(yearly)
Should be more efficient than just iterating through all days between start and end and checking, if that date ok.
If you have a Class LockedDate like this:
public class LockedDate
{
public DateTime Date { get; set; }
public bool IsYearly { get; set; }
...
}
Than you could use this code to get your needed dates:
List<DateTime> GetDateRange(List<LockedDate> source, DateTime start, DateTime end)
{
List<DateTime> dt = new List<DateTime>();
foreach(LockedDate d in source)
{
if(!d.IsYearly)
{
if(start<=d.Date && d.Date<=end)
dt.Add(d.Date);
}
else
{
for(DateTime i = new DateTime(start.Year,d.Date.Month,d.Date.Day);i<=new DateTime(end.Year,d.Date.Month,d.Date.Day);i=i.AddYears(1))
{
dt.Add(i);
}
}
}
return dt;
}

Categories

Resources