How to simplify logic when week starts from Monday? - c#

I have this code to get date in a week. But this code takes Monday as hardcoded. How to make those 4 lines flexible where its commented "//Special check for Sunday because acc. to our business week Starts from Monday and DayOfWeek Enum starts from Sunday."
I mean can var be calculated in a single line without if else condition? So that tomorrow if its decided that Sunday is Start Date, then no changes needs to be done.
class Program
{
static void Main(string[] args)
{
DateTime weekDate = GetWeekDate(DateTime.Now, DayOfWeek.Friday);
Console.WriteLine(weekDate); //prints 14-02-2014
Console.ReadLine();
}
private static DateTime GetWeekDate(DateTime dt, DayOfWeek dow)
{
DateTime sow;
int var = 0;
sow = StartOfWeek(dt);
if (dow == 0) //Special check for Sunday because acc. to our business week Starts from Monday and DayOfWeek Enum starts from Sunday.
var = 6;
else
var = (int)dow - 1;
return sow.AddDays(var);
}
public static DateTime StartOfWeek(DateTime dt)
{
int days = dt.DayOfWeek - DayOfWeek.Monday; //Week Starts from Monday
if (days < 0)
days += 7;
return dt.AddDays(-1 * days).Date;
}
}

It's called the modulo operator - % (also known as integral division remainder).
For example, (7 + dow - 1) % 7 should give you the proper value.
To shift the start of week, you just have to change the value you subtract, eg. to start the week with sunday, it will be zero, to start with monday it will be 1, to start with tuesday it will be 2 ((7 + dow - 2) % 7) etc.

What you can do is depend on a constant, or even accept as parameter, the start of the business week, and use that value in your calculations instead of hard-coding it.

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;
}

Get next given day after a month in c#

How can I get the next particular day after a month in c#,
for example if today is Monday I need to get the first Monday after 1 month from now, but in more generic way, for example if today is 17/11/2021 which is Wednesday I need to get the first Wednesday after a month from now.
I am using this function but it will return for next week and not next month:
public static DateTime GetNextWeekday(DateTime start, DayOfWeek day)
{
// The (... + 7) % 7 ensures we end up with a value in the range [0, 6]
int daysToAdd = ((int)day - (int)start.DayOfWeek + 7) % 7;
return start.AddDays(daysToAdd);
}
Typical month has 30 or 31 days, however, the next same day of week will be after either 28 (4 * 7) or 35 (5 * 7) days. We need a compromise. Let for February add 28 days and for all the other months add 35 days:
public static DateTime GetSameDayAfterMonth(DateTime start) =>
start.AddDays((start.AddMonths(1) - start).TotalDays < 30 ? 28 : 35);
Sure, you can elaborate other rules: say, when after adding 28 days we are still in the same month (1 May + 28 days == 29 May) we should add 35:
public static DateTime GetSameDayAfterMonth(DateTime start) =>
start.AddDays(start.Month == start.AddDays(28).Month ? 35 : 28);
Add a month, instead of a week to the start date:
public static DateTime GetSameDayAfterMonth(DateTime start)
{
var compare = start.AddMonths(1);
// The % 7 ensures we end up with a value in the range [0, 6]
int daysToAdd = ((int)start.DayOfWeek - (int)compare.DayOfWeek) % 7;
return compare.AddDays(daysToAdd);
}
Example:
var dayInNextMonth = GetSameDayAfterMonth(DateTime.Parse("2021-11-17")); // 2021-12-15
var weekday = dayInNextMonth.DayOfWeek; // Wednesday

Return the correct number of weeks for the Gregorian Calendar

I have a problem with the calendar that I use to get me a smaller number of weeks for certain months
For example, this happens to me at Sept 2019, where my number is 5 or in July 2018, which is also 5.
How can I fix this?
this is my current code:
private DateTime _calendarDate;
int numWeeks = NumberOfWeeks(_calendarDate.Year, _calendarDate.Month);
private int NumberOfWeeks(int year, int month)
{
return NumberOfWeeks(new DateTime(year, month, DateTime.DaysInMonth(year, month)));
}
private int NumberOfWeeks(DateTime date)
{
var beginningOfMonth = new DateTime(date.Year, date.Month, 1);
while (date.Date.AddDays(1).DayOfWeek != CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek)
date = date.AddDays(1);
return (int)Math.Truncate(date.Subtract(beginningOfMonth).TotalDays / 7f) + 1;
}
additional information on CultureInfo
The problem is that it always comes back to me one week less, so my calendar doesn't display it properly and then I get the error,
Here's an example for April 2018 where you give me 5 weeks, and then another one is missing and that's why I'm getting the error
can anyone guess how i could solve this problem?
When you can use System.Globalization.Calendar you can get the count of weeks in a month by using GetWeekOfYear for the first and the last day of the month and then calculate the difference (and add 1 to include the first week).
This would change your NumberOfWeeks() to the following:
private int NumberOfWeeks(DateTime date)
{
Calendar calendar = CultureInfo.CurrentCulture.Calendar;
var firstOfMonth = new DateTime(date.Year, date.Month, 1);
var week1 = calendar.GetWeekOfYear(firstOfMonth, CalendarWeekRule.FirstDay, DayOfWeek.Monday);
var week2 = calendar.GetWeekOfYear(date, CalendarWeekRule.FirstDay, DayOfWeek.Monday);
int numberOfWeeks = (week2 - week1) + 1;
return numberOfWeeks;
}
For your example (April 2018) this will give you 6 as a result and for May 2018 it will give you 5.
MSDN GetWeekOfYear
Here is a bit more mathematical solution:
int myYear = 2019, myMonth = 12; // the example for December 2019
var firstMonthDay = new DateTime(myYear, myMonth, 1);
int delta = firstMonthDay.DayOfWeek - System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek;
if (delta < 0)
delta += 7;
int daysInMonth = DateTime.DaysInMonth(myYear, myMonth);
int weekLinesNumber = (int)Math.Ceiling((daysInMonth + delta) / 7F); // the result
We count the delta (could be from 0 to 6), this is the number of days from the first day of the week to the first next month day. Then we count the resulting number of the weeks using Math.Ceiling function.
In short, we add to the month some delta days at the beginning in order to get new Extra Month block starting from the First Day of a Week (according to the culture). And then we count the total number of the occupied week lines. No need to add +1 when we use Math.Ceiling function.

How can I convert a workweek double into a DateTime in C#

I have a workweek number (a number from 1 to 52) and I want to convert this into a date.
For example:
3.1 = 01/13/2014
22.4 = 05/29/2014
My company uses workweeks where Monday would be .1 and tuesday would be .2 etc (Saturday and Sunday are .6 and .7)
Anybody know of a plugin that does this? I don't want to re-invent the wheel if I don't have to.
Update:
Here's a visual of the calendar that I'm talking about. The workweek varies each year which may make this challenging. Thanks
A brute force approach, probably could be done better
Find the monday that precedes the first day of the year, this is the week 1.
Now multiply 7 for the converted number of weeks - 1 (we don't start from zero), add the number of days less 1 and pass the value to AddDays to find the exact date required
void Main()
{
string k = "22.4";
Console.WriteLine(ConvertWeekDay(k, 2014).ToString());
k = "53.1";
Console.WriteLine(ConvertWeekDay(k, 2014).ToString());
k = "1.1";
Console.WriteLine(ConvertWeekDay(k, 2015).ToString());
}
public DateTime ConvertWeekDay(string weekday, int year)
{
int w; int d;
string[] wd = weekday.Split(new char[] {'.'});
w = int.Parse(wd[0]); d = int.Parse(wd[1]);
DateTime dt = new DateTime(year,1,1);
while(dt.DayOfWeek != DayOfWeek.Monday)
dt = dt.AddDays(-1);
dt = dt.AddDays(7 * (w - 1) + d - 1);
return dt;
}
Notice that the 53.1 and 1.1 refers to the same day if you change the year.

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 :-)

Categories