Calculate Monday-Sunday week from a DateTime object - c#

I'm trying to write a method that will return a list of DateTimes representing a Monday-Sunday week. Its supposed to take the DateTime given to it and use it to calculate the surrounding dates
It calculates the starting date ok but the problem begins when it hits the last loop. With each run through the DateTime variable tmpDate should be incremented by 1 day, then added to the list. However, as it stands I'm getting back a List containing 7 starting dates.
Can anyone see where I'm going wrong (I have the feeling I may be made to look like a bit of a simpleton on this :) )?
Also, apologies if this is an often-asked question. Could see plenty of Start date/End Date and week number type questions, but none specifically dealing with this type of problem.
private List<DateTime> getWeek(DateTime enteredDate)
{
/* Create List to hold the dates */
List<DateTime> week = new List<DateTime>();
int enteredDatePosition = (int)enteredDate.DayOfWeek;
/* Determine first day of the week */
int difference = 0;
for (int i = 0; i < 7; i++)
{
difference++;
if (i == enteredDatePosition)
{
break;
}
}
// 2 subtracted from difference so first and enteredDatePostion elements will not be counted.
difference -= 2;
DateTime startDate = enteredDate.Subtract(new TimeSpan(difference, 0, 0, 0));
week.Add(startDate);
/* Loop through length of a week, incrementing date & adding to list with each iteration */
DateTime tmpDate = startDate;
for (int i = 1; i < 7; i++)
{
tmpDate.Add(new TimeSpan(1, 0, 0, 0));
week.Add(tmpDate);
}
return week;
}

DateTimes are immutable.
tmpDate.Add(...) returns a new DateTime, and does not modify tmpDate.
You should write tmpDate = tmpDate.AddDays(1) or tmpDate += TimeSpan.FromDays(1)

I believe this snippet is tailored towards Sunday trough Saturday, but you can try something like this:
DateTime ToDate = DateTime.Now.AddDays((6 - (int)DateTime.Now.DayOfWeek) - 7);
DateTime FromDate = ToDate.AddDays(-6);

You're making your algorithm more complex than it needs to be. Check out this working snippet:
using System;
using System.Collections.Generic;
public class MyClass
{
public static void Main()
{
// client code with semi-arbitrary DateTime suuplied
List<DateTime> week = GetWeek(DateTime.Today.AddDays(-2));
foreach (DateTime dt in week) Console.WriteLine(dt);
}
public static List<DateTime> GetWeek(DateTime initDt)
{
// walk back from supplied date until we get Monday and make
// that the initial day
while (initDt.DayOfWeek != DayOfWeek.Monday)
initDt = initDt.AddDays(-1.0);
List<DateTime> week = new List<DateTime>();
// now enter the initial day into the collection and increment
// and repeat seven total times to get the full week
for (int i=0; i<7; i++)
{
week.Add(initDt);
initDt = initDt.AddDays(1.0);
}
return week;
}
}

Related

Calculate exact no of week for date range

A person is engaged in different works in different time duration which will start from Monday and end on friday as follows.Monday to Friday will be considered as 1 week.Any overlapping weeks be considered as 1 week.
Below are the scenario
"AssignedEngagementdate":[
{"Startdate":"01/03/2022","Enddate":"01/07/2022"},
{"Startdate":"01/10/2022","Enddate":"01/14/2022"},
{"Startdate":"01/10/2022","Enddate":"01/21/2022"},
{"Startdate":"02/14/2022","Enddate":"02/18/2022"}
]
Here I need to find the no of weeks assigned by this person and it should be 4 since one week is from 10th Jan to 14 Feb is overlapping in 2 engagement.
How I can do this in C# using linq. I was trying to fetch min start date and max end date from list and find the difference and converting in no of weeks but it has not given the actual result since date assigned is not consistent.
The trick is to convert each date range into an enumerated week range, or at least a list of distinct dates (such as Sundays) corresponding to each week or partial week. SelectMany() lets you gather them all together, after which a distinct count gives you an answer.
Try something like:
int numberOfWeeks = AssignedEngagementdate
.SelectMany(a => {
DateTime firstSunday = a.Startdate.AddDays(-(int)a.Startdate.DayOfWeek);
DateTime lastSunday = a.Enddate.AddDays(-(int)a.Enddate.DayOfWeek); // May be same as firstSunday
int weeks = (lastSunday - firstSunday).Days / 7 + 1;
// Enumerate one Sunday per week
return Enumerable.Range(0, weeks).Select(i => firstSunday.AddDays(7 * i));
})
.Distinct()
.Count();
You may still need to consider and test cases where assignments start or end on days other than Monday and Friday. The above should handle most such cases.
Without linq, you can try this Datetime extension :
public static int NumberOfWeeksBetween(this DateTime start, DateTime end)
{
TimeSpan span = end.Subtract(start);
if (span.Days <= 7)
{
if (start.DayOfWeek > end.DayOfWeek) return 2;
else return 1;
}
int days = span.Days - 7 + (int)start.DayOfWeek;
int weekCount = 1;
int dayCount = 0;
for (weekCount = 1; dayCount < days; weekCount++) dayCount += 7;
return weekCount;
}

How to get next working day with the input of number of days? [duplicate]

This question already has answers here:
How to get the next working day, excluding weekends and holidays
(5 answers)
Closed 3 years ago.
I set up some methods to get holidays and used them to calculate next working day. This works fine but it only adds 1 day and gets the next non holiday weekday. I want to get the number of days by input and increment that date according to input and get the nex non holiday weekday. I tried some things but couldnt get exactly what i want.
Edit: I am using a method to get the next working day, excluding weekends and holidays. But my equation does that by incrementing the date only by 1 day. I want that number of days to be my input from user, and add that number to the date, exclude weekends and holidays and get the next working day. (My issue is on the adding that different number from my textbox)
public static DateTime GetWorkingDay(DateTime date, IList<Holiday> holidays, IList<DayOfWeek> weekendDays)
{
date = date.Date.AddDays(1);
var holidayDates = holidays.Select(x => x.GetDate(date.Year))
.Union(holidays.Select(x => x.GetDate(date.Year + 1)))
.Where(x => x != null)
.Select(x => x.Value)
.OrderBy(x => x).ToArray();
while (true)
{
if (weekendDays.Contains(date.DayOfWeek) || holidayDates.Contains(date))
date = date.AddDays(1);
else
return date;
}
}
You could change your while loop to (assuming numDays is your input):
while (numDays > 0)
{
date = date.AddDays(1);
if (!weekendDays.Contains(date.DayOfWeek) && !holidayDates.Contains(date))
numDays--; // Only decrement numDays if it's a working day.
}
Then return the date after the loop. You might want to assert that numDays isn't negative.
Here's an approach that might help. Or it might seem like total overkill. You decide.
Instead of trying to add one day at a time, figure out if it's a holiday, weekday, etc., just create a list of dates that you can query. That will make the logic much easier to understand, especially if you find yourself having to write more and more queries like this.
Ideally this is something you'd want to create once and keep around. It wouldn't make sense to keep creating this over and over.
Here's a stab at it:
public class Calendar
{
private readonly List<CalendarDate> _dates = new List<CalendarDate>();
public Calendar(
DateTime startDate,
int length,
ICollection<DateTime> holidays,
ICollection<DayOfWeek> weekDays)
{
if(length < 1)
throw new ArgumentOutOfRangeException(nameof(length), "The minimum length is one day.");
for (var x = 0; x < length; x++)
{
var addingDate = new CalendarDate
{
RepresentedDate = startDate.Date.AddDays(x),
};
addingDate.IsHoliday = holidays.Contains(addingDate.RepresentedDate);
addingDate.IsWeekday = weekDays.Contains(addingDate.RepresentedDate.DayOfWeek);
_dates.Add(addingDate);
}
FirstDay = startDate.Date;
LastDay = FirstDay.AddDays(length - 1);
}
public DateTime FirstDay { get; }
public DateTime LastDay { get; }
private class CalendarDate
{
internal DateTime RepresentedDate { get; set; }
internal bool IsHoliday { get; set; }
internal bool IsWeekday { get; set; }
internal bool IsWorkingDay => !(IsHoliday || IsWeekday);
}
public DateTime GetNextWorkingDay(DateTime date)
{
date = date.Date;
ValidateDateIsInRange(date);
var result = _dates.FirstOrDefault(d =>
d.RepresentedDate >= date && d.IsWorkingDay);
if (result != null) return result.RepresentedDate;
throw new InvalidOperationException("The result is outside the range of the calendar.");
}
private void ValidateDateIsInRange(DateTime date)
{
if(date < FirstDay || date > LastDay)
throw new InvalidOperationException("The specified date is outside the range of the calendar.");
}
}
It admittedly looks like more work and more code. But the result is that once the data is in place, the calculation you're trying to do becomes very easy. So do all sorts of other calculations and queries, because now such functions can be written to reflect the questions that you're asking, like "What is the first date that matches this criteria?"

Extracting dates of current week using .Net?

I am trying to extract dates for current week's days and I just can't find a sensible, smart way instead of a long case, switches and if statements.
Anybody knows a relatively easy way to extract those using .Net?
Thanks!
The DateTime.DayOfWeek is an enumeration that starts with Sunday being 0 and going forward. If you take today's day-of-week, it will also tell how many days ago Sunday was. Therefore going back that many days will give you the Sunday of this week, assuming week starts on Sunday. You can go forward from that for the seven days of the week.
var today = DateTime.Now;
var thisSunday = today.AddDays(-(int)today.DayOfWeek);
for (int i=0; i<7; i++)
Console.WriteLine(thisSunday.AddDays(i).ToString());
If the week starts from Monday, use
var thisMonday = today.AddDays(-(((int)today.DayOfWeek + 6) % 7));
You may use extension method to set the day that week start with (credit goes to #Compile This)
public static class DateTimeExtensions
{
public static DateTime StartOfWeek(this DateTime datetime, DayOfWeek startOfWeek)
{
int difference = datetime.DayOfWeek - startOfWeek;
if (difference >= 0)
return datetime.AddDays(-1 * difference).Date;
difference += 7;
return datetime.AddDays(-1 * difference).Date;
}
}
Then you can get date of the week using same loop as #Sami_Kuhmonen mentioned:
DateTime d = DateTime.Now.StartOfWeek(DayOfWeek.Saturday);
for (int i = 0; i < 7; i++)
Console.WriteLine(d.AddDays(i));

Is this a good algorithm to find each week in a given month?

I need to take a month (defined as a start and end date) and return a set of date ranges for each week in that month. A week is defined as Sunday through Saturday. A good way to visualize it is if you double click on your Windows date in the start bar:
The month of October 2011 has 6 weeks: 10/1-10/1, 10/2-10/8, 10/9-10/15, 10/16-10/22, 10/23-10/29 and 10/30-10/31.
I can describe each week as a struct:
struct Range
{
public DateTime Start;
public DateTime End;
public Range(DateTime start, DateTime end)
{
Start = start;
End = end;
}
}
I need to write a function that takes a month and returns an array of ranges within it. Here's my first attempt, which appears to work and addresses the obvious edge cases:
public static IEnumerable<Range> GetRange(DateTime start, DateTime end)
{
DateTime curStart = start;
DateTime curPtr = start;
do
{
if (curPtr.DayOfWeek == DayOfWeek.Saturday)
{
yield return new Range(curStart, curPtr);
curStart = curPtr.AddDays(1);
}
curPtr = curPtr.AddDays(1);
} while (curPtr <= end);
if(curStart <= end)
yield return new Range(curStart, end);
}
I would like to know if there's a cleaner or more obvious approach to do the same. I'm not overly concerned about performance, but I'd like to improve code readability and make the algorithm a bit more concise. Perhaps there's a very creative solution involving a single LINQ expression or something. Thanks!
This is based on simply incrementing by 7, as suggested by Previti, ready for international use. If your C# is < 4.0, remove the default parameter = DayOfWeek.Sunday
public static IEnumerable<Range> GetRange(DateTime start, DateTime end, DayOfWeek startOfTheWeek = DayOfWeek.Sunday)
{
if (start > end)
{
throw new ArgumentException();
}
// We "round" the dates to the beginning of the day each
start = start.Date;
end = end.Date;
// The first week. It could be "shorter" than normal. We return it "manually" here
// The 6 + startOfWeek - start.DayOfWeek will give us the number of days that you
// have to add to complete the week. It's mod 7. It's based on the idea that
// the starting day of the week is a parameter.
DateTime curDay = new DateTime(Math.Min(start.AddDays((6 + (int)startOfTheWeek - (int)start.DayOfWeek) % 7).Ticks, end.Ticks), start.Kind);
yield return new Range(start, curDay);
curDay = curDay.AddDays(1);
while (curDay <= end)
{
// Each time we add 7 (SIX) days. This is because the difference between
// as considered by the problem, it's only 6 * 24 hours (because the week
// doesn't end at 23:59:59 of the last day, but at the beginning of that day)
DateTime nextDay = new DateTime(Math.Min(curDay.AddDays(6).Ticks, end.Ticks), start.Kind);
yield return new Range(curDay, nextDay);
// The start of the next week
curDay = nextDay.AddDays(1);
}
}
Some small notes: Math.Min isn't defined for DateTime, so I cheat a little by taking the Ticks of the DateTimes and comparing them. Then I rebuild the DateTime. I always use the DateTimeKind of the start date.
When you debug yield code, remember to "materialize" the result through the use of ToList or ToArray, otherwise the code won't be executed :-)

How can I get correct payperiod from date?

I feel like this is math problem more than anything. My company has employees all over the country. Some parts of the company are on an "odd" pay cycle and some are on "even". I call the starting date of a given pay period a "payperiod". I need to do two things:
1) determine the payperiod in which a given date falls
//Something like this:
public static DateTime getPayPeriodStartDate(DateTime givenDate, string EvenOrOdd)
{ .. }
2) get a list of payperiods between two dates:
//Something like this:
public static List<DateTime> getPayPeriodsBetween(DateTime start, DateTime end, string EvenOrOdd)
{ .. }
I'm using a couple dates as fixed standards on which to base any future pay period dates. The fixed standard dates for even and odd are as follows:
Even - 01/04/09
Odd - 01/11/09
Each pay period starts on the sunday of the week and goes for two weeks. For instance, using the standard dates above, the first even pay period starts on 01/04/09 and ends on 01/17/09. The first odd pay period starts on 01/11/09 and ends on 01/24/09. As you can see, there is some overlap. We have thousands of employees so it's necessary to split them up a bit.
I have a solution that is based on week numbers but it's clunky and has to be "fixed" every new year. I'm wondering how you would handle this.
Not fully optimized or tested, but this is what I came up with:
const int DaysInPeriod = 14;
static IEnumerable<DateTime> GetPayPeriodsInRange(DateTime start, DateTime end, bool isOdd)
{
var epoch = isOdd ? new DateTime(2009, 11, 1) : new DateTime(2009, 4, 1);
var periodsTilStart = Math.Floor(((start - epoch).TotalDays) / DaysInPeriod);
var next = epoch.AddDays(periodsTilStart * DaysInPeriod);
if (next < start) next = next.AddDays(DaysInPeriod);
while (next <= end)
{
yield return next;
next = next.AddDays(DaysInPeriod);
}
yield break;
}
static DateTime GetPayPeriodStartDate(DateTime givenDate, bool isOdd)
{
var candidatePeriods = GetPayPeriodsInRange(givenDate.AddDays(-DaysInPeriod), givenDate.AddDays(DaysInPeriod), isOdd);
var period = from p in candidatePeriods where (p <= givenDate) && (givenDate < p.AddDays(DaysInPeriod)) select p;
return period.First();
}
I haven't tested for many test cases, but I think this fits the bill:
public static DateTime getPayPeriodStartDate(DateTime givenDate, string EvenOrOdd)
{
DateTime newYearsDay = new DateTime(DateTime.Today.Year, 1, 1);
DateTime firstEvenMonday = newYearsDay.AddDays((8 - (int)newYearsDay.DayOfWeek) % 7);
DateTime firstOddMonday = firstEvenMonday.AddDays(7);
TimeSpan span = givenDate - (EvenOrOdd.Equals("Even") ? firstEvenMonday : firstOddMonday);
int numberOfPayPeriodsPast = span.Days / 14;
return (EvenOrOdd.Equals("Even") ? firstEvenMonday : firstOddMonday).AddDays(14 * numberOfPayPeriodsPast);
}
public static List<DateTime> getPayPeriodsBetween(DateTime start, DateTime end, string EvenOrOdd)
{
DateTime currentPayPeriod = getPayPeriodStartDate(start, EvenOrOdd);
if (currentPayPeriod < start) currentPayPeriod = currentPayPeriod.AddDays(14);
List<DateTime> dtList = new List<DateTime>();
while (currentPayPeriod <= end)
{
dtList.Add(currentPayPeriod);
currentPayPeriod = currentPayPeriod.AddDays(14);
}
return dtList;
}
I am sure it can be improved.
I had a need to do something similar and was able to do it very easily using LINQ. Simply build up a List for even and odd and then query between dates from the odd/even as necessary. Also, I recommend you move to an emum for parameters like EvenOrOdd where you have fixed values.
I had a similar problem a few months ago, and I ended up writing a quick script to create entries in a database for each pay period so I never had to actually do the math. This way, The system works the same speed, and doesn't have to do any slow iterations every time a period is requested.
That being said, you can always take the starting date, and add two weeks (or however long your periods are) over and over until you reach the dates you specify in the function call. This is a bit ugly, and the longer it sits in production, the slower it gets (since the dates are getting further and further apart).
Both ways are trivial to implement, it's just a matter of what kind of resources you have at hand to tackle the issue.
So, for number 1: Start with either 1/4/2009 or 1/11/2009 (depending on even/odd pay week) and add 2 weeks until the givenDate is less than the date you're testing + 2 weeks. That's the start of the period.
For number 2: Same thing, start at the date and add 2 weeks until you're within the date range. While you're there, add each item to a list. As soon as you're past the last date, break out of your loop and return your shiny new list.
If you used my method and went with a database to house all this info, it turns into 2 simple queries:
1)SELECT * FROM payperiods WHERE startdate<=givenDate ORDER BY startdate LIMIT 1
2) SELECT * FROM payperiods WHERE startdate>=givenDate AND enddate<=givenDate ORDER BY startdate
It works perfectly. I have tested.
public static DateTime GetFirstDayOfWeek(DateTime dayInWeek)
{
CultureInfo _culture = (CultureInfo)CultureInfo.CurrentCulture.Clone();
CultureInfo _uiculture = (CultureInfo)CultureInfo.CurrentUICulture.Clone();
_culture.DateTimeFormat.FirstDayOfWeek = DayOfWeek.Monday;
_uiculture.DateTimeFormat.FirstDayOfWeek = DayOfWeek.Monday;
System.Threading.Thread.CurrentThread.CurrentCulture = _culture;
System.Threading.Thread.CurrentThread.CurrentUICulture = _uiculture;
// CultureInfo defaultCultureInfo = CultureInfo.CurrentCulture;
DayOfWeek firstDay = _culture.DateTimeFormat.FirstDayOfWeek;
DateTime firstDayInWeek = dayInWeek.Date;
// Logic Of getting pay period Monday(Odd monday)
int i = Convert.ToInt32(firstDay);
while (firstDayInWeek.DayOfWeek != firstDay)
if (i % 2 != 0)
{ firstDayInWeek = firstDayInWeek.AddDays(-1); }
else
{
firstDayInWeek = firstDayInWeek.AddDays(-2);
}
return firstDayInWeek;
}

Categories