I need to count the total days worked given struct saying which days of the week are worked and a from and to date.
My current algorithm is this:
protected int TotalWorkDays(DateTime From, DateTime dtTo,WorkedDays days)
{
int Days = 0;
DayOfWeek DOW;
for (DateTime curDate = From; curDate.Date <= dtTo.Date; curDate = curDate.AddDays(1))
{
DOW = curDate.DayOfWeek;
if ((DOW == DayOfWeek.Sunday & days.Sunday) |
(DOW == DayOfWeek.Monday & days.Monday) |
(DOW == DayOfWeek.Tuesday & days.Tuesday) |
(DOW == DayOfWeek.Wednesday & days.Wednesday) |
(DOW == DayOfWeek.Thursday & days.Thursday) |
(DOW == DayOfWeek.Friday & days.Friday) |
(DOW == DayOfWeek.Saturday & days.Saturday)
)
{
Days += 1;
}
}
return Days;
}
I'm almost positive this can be done without a loop, but I can't seem to figure out. Can someone help me find a more efficient algorithm?
Find the number of weeks between the From and To dates (using subtraction and division). Then multiply that by the number of days worked per week. Do some subtraction for the end cases (From/To dates are in the middle of a week).
hmmmm....
Create a dictionary going from DayOfWeek (int if i remember correctly), to bool then....
var DaysWorked = (from dayoffset in Enumerable.Range(0, (To - From).TotalDays)
where WorkingDays[From.AddDays(dayoffset).DayOfWeek]
select dayoffset).Count();
Not exactly efficient though!
Have a look at this codeproject article which explains how to do it without looping ;)
EDIT: Here's the formula it uses:
Calculate the number of time span in terms of weeks. Call it, W.
Deduct the first week from the number of weeks. W= W-1
Multiply the number of weeks with the number of working days per week.
Call it, D.
Find out the holidays during the specified time span. Call it, H.
Calculate the days in the first week. Call it, SD.
Calculate the days in the last week. Call it, ED.
Sum up all the days. BD = D + SD + ED � H.
You can take advantage of the fact that days of the week repeat every seven days. Here is a basic outline of an algorithm:
Count the number of days worked in the first partial week.
Count the number of days worked in the last partial week.
Calculate the number of whole weeks in the middle and multiply by number of days worked in a week.
Sum the above three values.
var workDays = new DayOfWeek[]{ DayOfWeek.Monday, DayOfWeek.Tuesday};
var days = TotalWorkDays(new DateTime(2005,1,12), new DateTime(2005,3,15), workDays);
protected int TotalWorkDays(DateTime start, DateTime end, DayOfWeek[] workDays)
{
var weeks = (int)Math.Floor((end - start).TotalDays / 7);
var days = weeks * workDays.Length;
//Calc rest
var d = start.AddDays(weeks * 7);
while (d <= end)
{
if(workDays.Contains(d.DayOfWeek))
days++;
d = d.AddDays(1);
}
return days;
}
You can use the following algorithm:
count the working days of the starting week (max 7 iterations)
count the weeks between start/end and multiple the weeks with the working days
count the working days of the end week (max 7 iterations)
The sample uses the classes Week and DateDiff of the Time Period Library for .NET
// ----------------------------------------------------------------------
public int CountWorkingDays( DateTime start, DateTime end, IList<DayOfWeek> workingDays )
{
if ( workingDays.Count == 0 )
{
return 0;
}
Week startWeek = new Week( start );
Week endWeek = new Week( end );
int dayCount = 0;
// start week
DateTime currentDay = start.Date;
while ( currentDay < startWeek.End )
{
if ( workingDays.Contains( currentDay.DayOfWeek ) )
{
dayCount++;
}
currentDay = currentDay.AddDays( 1 );
}
// between weeks
DateDiff inBetweenWeekDiff = new DateDiff( startWeek.End, endWeek.Start );
dayCount += inBetweenWeekDiff.Weeks * workingDays.Count;
// end week
currentDay = endWeek.Start.Date;
while ( currentDay < end )
{
if ( workingDays.Contains( currentDay.DayOfWeek ) )
{
dayCount++;
}
currentDay = currentDay.AddDays( 1 );
}
return dayCount;
} // CountWorkingDays
Usage:
// ----------------------------------------------------------------------
public void CountWorkingDaysSample()
{
DayOfWeek[] workingDays = new [] { DayOfWeek.Monday, DayOfWeek.Tuesday };
DateTime start = new DateTime( 2011, 3, 1 );
DateTime end = new DateTime( 2011, 5, 1 );
Console.WriteLine( "working days: {0}", CountWorkingDays( start, end, workingDays ) );
// > working days: 19
} // CountWorkingDaysSample
Related
I have two fields startdate and enddate where I need to calculate how many weekends in between those two dates and show them in minutes. For example start date is 01/11/2019 00:00:00 and end date as 03/11/2019 12:00:00, I should get the output in total Saturday and partial Sunday as 1.5 days weekend in between the given dates
I tried the following code which is not calculating the time on weekends with given scenario
public int CountOfWeekEnds(DateTime startDate, DateTime endDate)
{
int weekEndCount = 0;
if (startDate > endDate)
{
DateTime temp = startDate;
startDate = endDate;
endDate = temp;
}
TimeSpan diff = endDate - startDate;
int days = diff.Days;
for (var i = 0; i <= days; i++)
{
var testDate = startDate.AddDays(i);
if (testDate.DayOfWeek == DayOfWeek.Saturday || testDate.DayOfWeek == DayOfWeek.Sunday)
{
if (testDate.Minute > 0)
{
weekEndCount += 1;
}
}
}
return weekEndCount;
}
Showing output as 2 days of weekend instead of 1.5 days in between the dates. Please suggest how I achieve this
If I understand correctly, by weekends you mean both saturdayand sunday.
I use this code to compute how many DayOfWeek exists between two dates.
public static int CountOfWeekEnds(DateTime start, DateTime end) {
return CountDays(DayOfWeek.Saturday, start, end) + CountDays(DayOfWeek.Sunday, start, end);
}
public static int CountDays(DayOfWeek day, DateTime start, DateTime end)
{
TimeSpan ts = end - start; // Total duration
int count = (int)Math.Floor(ts.TotalDays / 7); // Number of whole weeks
int remainder = (int)(ts.TotalDays % 7); // Number of remaining days
int sinceLastDay = end.DayOfWeek - day; // Number of days since last [day]
if (sinceLastDay < 0) sinceLastDay += 7; // Adjust for negative days since last [day]
// If the days in excess of an even week are greater than or equal to the number days since the last [day], then count this one, too.
if (remainder >= sinceLastDay) count++;
return count;
}
Reference
There are a few things you should change to make this work:
Since we want to return the number of weekend days as a decimal, we need to change our return type to something that represents that, like a double.
Then, when we calculate our days, we need to get the fraction of the day by dividing the hours by 24. In my example below, I went even further and calculated the fraction of days based on the number of Ticks instead of the Hours.
And finally, when we add days, we should use only the Date part of the result so that the time is set to midnight, except for the very last day, where we want to use the specified time.
For example:
public static double GetWeekendDaysCount(DateTime start, DateTime end)
{
if (start == end) return 0;
if (start > end)
{
DateTime temp = start;
start = end;
end = temp;
}
double weekendDays = 0;
var current = start;
// To be super accurate, we can calculate based on Ticks instead of hours
var ticksInADay = (double)TimeSpan.FromDays(1).Ticks;
while (current <= end)
{
if (current.DayOfWeek == DayOfWeek.Saturday ||
current.DayOfWeek == DayOfWeek.Sunday)
{
// If the time is midnight, count it as one day,
// otherwise add a fraction of a day
weekendDays += current.TimeOfDay > TimeSpan.Zero
? current.TimeOfDay.Ticks / ticksInADay
: 1;
}
// Add a day and set the time to midnight by using 'Date'
current = current.AddDays(1).Date;
// Unless we're on the last day, then we want
// to use the TimeOfDay that was specified
if (current == end.Date) current = end;
}
return weekendDays;
}
In a scenario where you would need to calculate the next 'Billing date' if the DAY (2nd, 25th, etc) is known, how can you calculate the number of days left until the next bill payment?
Explanation:
Tom's bill gets generated on the 4th of every month
What's the best way/logic to calculate the days left until the next bill? For example, if today is the 28th of this month, the result would be 6 days left
What we know:
Bill Generation Date is known
Today's Date is known
What I've done so far:
int billingDay = 4; //The day the bill gets generated every month
DateTime today = DateTime.Today; //Today's date
How would I continue this to calculate the next billing date?
P.S: Sorry if this sounds lame, I just couldn't wrap my head around it :)
I think this works:
private int GetNumDaysToNextBillingDate(int billingDayOfMonth)
{
DateTime today = DateTime.Today;
if (today.Day <= billingDayOfMonth)
{
return (new DateTime(today.Year, today.Month, billingDayOfMonth) - today).Days;
}
else
{
var oneMonthFromToday = today.AddMonths(1);
var billingDateNextMonth =
new DateTime(oneMonthFromToday.Year,
oneMonthFromToday.Month, billingDayOfMonth);
return (billingDateNextMonth - today).Days;
}
}
How about:
int billingDay = 4;
DateTime today = DateTime.UtcNow;
DateTime billing = today.Day >= billingDay
? new DateTime(today.AddMonths(1).Year, today.AddMonths(1).Month, billingDay)
: new DateTime(today.Year, today.Month, billingDay);
TimeSpan left = billing - today;
This uses a loop but is less prone to error as it takes into account month and year changes:
int DaysUntilBilling(int billingDay, DateTime referenceDate)
{
int count = 0;
while (referenceDate.AddDays(count).Day != billingDay)
{
count++;
};
return count;
}
You of course don't need to pass a DateTime in as an argument if you are always using today's date, but this helps to test that that for different inputs, you get the desired output:
int billingDay = 4;
DaysUntilBilling(billingDay, DateTime.Now); //26 (today is 9th Aug 2016)
DaysUntilBilling(billingDay, new DateTime(2016, 09, 03); //1
DaysUntilBilling(billingDay, new DateTime(2016, 09, 04); //0
DaysUntilBilling(billingDay, new DateTime(2016, 08, 05); //30
DaysUntilBilling(billingDay, new DateTime(2016, 12, 19); //16
This link might help you :
https://msdn.microsoft.com/en-us/library/system.datetime.daysinmonth(v=vs.110).aspx
What you can do is something like this:
int daysUntilBill = 0;
int billingDay = 4;
DateTime today = DateTime.Today;
if (billingDay > today.Day) {
daysUntilBill = billingDay - today.Day;
} else {
int daysLeftInMonth = DateTime.DaysInMonth(today.Year, today.Month) - today.Day;
daysUntilBill = billingDay + daysLeftInMonth;
}
or slightly more concise
int daysUntilBill = (billingDay >= today.Day)
? billingDay - today.Day
: billingDay + DateTime.DaysInMonth(today.Year, today.Month) - today.Day;
This properly handles the year ending too, since it doesn't try to wrap around.
First you need to determine if the current date is on or before the billing day and if it is just subtract the current day of the month. Otherwise you have to determine the next billing date in the following month.
public int DaysToNextBill(int billingDay)
{
var today = DateTime.Today;
if(today.Day <= billingDay)
return billingDay - today.Day;
var nextMonth = today.AddMonth(1);
var nextBillingDate = new DateTime(nextMonth.Year, nextMonth.Month, billingDay)
return (nextBillingDate - today).Days;
}
The only thing left to deal with is if billingDay is greater than the number of days in the current or following month.
I have been trying to find the 5th week date of a day in a month like 5th week Monday date, 5th week Tue date, Wed... and so on based on the date from the same month. This date could belong to any week of same month.
I tried like
DateTime MonthEventDate=05/01/2016; //Date format in dd/MM/yyyy format
DayOfWeek EventDay="Sunday"; //For Example i want to find 5th Sunday in January Month, but days too will change everytime based on user selection
string SelectedWeek="5"; //Here i'm getting the week in which i need to find the given date i.e, 5th Monday or Tuesday & so on
if (SelectedWeek == "5")
{
//Here i tried to add number of days to my initial day to find 5th day date, but every time its returning next month value
MonthEventDate = MonthEventDate.AddDays((EventDay < MonthEventDate.DayOfWeek ? 31 : 28) + EventDay - MonthEventDate.DayOfWeek);
}
I know the logic is wrong but i want to get date of 5th day of the week, and if that day is not present, return 0. Looking for some guidance
Note: Here Month will change based on User Input, so how to return Date of fifth day, if it exist in the given month
This should give you the 5th day (if there is one) ...
DateTime dayInMonth = DateTime.Now;
DayOfWeek dayToFind = DayOfWeek.Friday;
var fifth= Enumerable.Range(1, DateTime.DaysInMonth(dayInMonth.Year, dayInMonth.Month))
.Select(day => new DateTime(dayInMonth.Year, dayInMonth.Month, day))
.Where(day => day.DayOfWeek == dayToFind)
.Skip(4)
.FirstOrDefault();
Extending #Soner Gönül's answer. In input you put required day of week, required month and required year. On output you get date of same day of week in fifth weeks, or null
public DateTime? FifthDay(DayOfWeek dayOfWeek, byte monthNum, int year)
{
if (monthNum > 12 || monthNum < 1)
{
throw new Exception("Month value should be between 1 and 12");
}
var searchDate = new DateTime(year, monthNum, 1);
var weekDay = searchDate.DayOfWeek;
if (weekDay == dayOfWeek)
{
searchDate = searchDate.AddDays(28);
}
for (DateTime d = searchDate; d < d.AddDays(7); d = d.AddDays(1))
{
if (d.DayOfWeek == dayOfWeek)
{
searchDate = searchDate.AddDays(28);
break;
}
}
if (searchDate.Month == monthNum)
return searchDate;
return null;
}
You can use a simple math like this (no validations included)
static DateTime? FindDate(int year, int month, DayOfWeek dayOfWeek, int weekOfMonth = 5)
{
var baseDate = new DateTime(year, month, 1);
int firstDateOffset = ((int)dayOfWeek - (int)baseDate.DayOfWeek + 7) % 7;
var date = baseDate.AddDays(firstDateOffset + 7 * (weekOfMonth - 1));
return date.Month == month ? date : (DateTime?)null;
}
I think the code is self explanatory. The only trick that probably needs explanation is the line
int firstDateOffset = ((int)dayOfWeek - (int)baseDate.DayOfWeek + 7) % 7;
which handles the case when let say the month starts in Friday and you asked for Monday, and is a short equivalent of
int firstDateOffset = (int)dayOfWeek - (int)baseDate.DayOfWeek;
if (firstDateOffset < 0) firstDateOffset += 7;
Usage in your example
var monthEventDate = FindDate(2016, 1, DayOfWeek.Sunday, 5);
You can refer this demo code
int requiredDay = 5; //Day number i.e 0-6(Sunday to SaturDay)
DateTime day = new DateTime(2016, 1, 1); //Month,year,Date
if (DateTime.DaysInMonth(day.Year, day.Month) > 28)
{
//Get the first day name of the month
int firstMonthDay = (int)day.DayOfWeek;
int offset=0;
//Number of days from the first day for the required day
if (firstMonthDay <= requiredDay)
offset = requiredDay - firstMonthDay;
else
offset = 7 - firstMonthDay + requiredDay;
//
DateTime firstoccurence = day.AddDays((double)offset);
DateTime fifthOccurence = firstoccurence.AddDays(28);
if (fifthOccurence.Month == firstoccurence.Month)
MessageBox.Show(fifthOccurence.ToString());
else
MessageBox.Show("No 5th Occurence for this day");
}
I am trying to work out how many days are in each week across a date range. my week runs sat-sat.
I have a startdate, and a duration (and a week number - as actually I want to return the number of days in a particular week across the range)
edit: I will try to be a bit more clear. I do indeed want the days before the next saturday across a date range .. so yes when its a range that is long enough the second week will always be 7 days. however the last week will be the number of days remaining of the duration if the duration does not stretch until a saturday.. hopefully the answers below will shed some more light.. maybe not though.
week number is the week across the date range. nothing to do with where weeks fall in the year.
worth noting that the arrivalDate can be a saturday so if its a saturday start and 7 duration would need to return 7...
so basically if I have say
arrivalDate = 29/06/2014 (sunday)
weeknumber = 1
duration = 17
I need it to return 6
if the weeknumber is 2 it should return 7
if the weeknumber is 3 it should return 4
I have this so far, which I know is far from ideal - expecting a bit of recursion might work best but my brain isnt quite functioning as yet..
public static int DaysInWeek(DateTime arrivalDate, int weekNumber, int duration)
{
int ret =0;
List<int> arr = new List<int>();
int leftOver = 0;
for (int i = 1; i <= duration; i++)
{
if (arrivalDate.AddDays(i).DayOfWeek == DayOfWeek.Saturday)
{
int x = i;
arr.Add( x - arr.Sum() );
leftOver = duration - arr.Sum();
}
}
if (leftOver < 7 && leftOver > 0) arr.Add( leftOver );
if (arr.Count == 0) ret = duration;
else ret = arr[weekNumber - 1];
return ret;
}
thanks
Here is my solution based on CeejeeB's solution, that handles Saturday as first day and weekNumbers that are outside the timespan defined by duration.
public static int DaysInWeek(DateTime arrivalDate, int weekNumber, int duration)
{
// Handle a Saturday as start day
var daysInFirstWeek = arrivalDate.DayOfWeek == DayOfWeek.Saturday ? 7 : DayOfWeek.Saturday - arrivalDate.DayOfWeek;
// First week
if (weekNumber == 1) return Math.Min(duration, daysInFirstWeek);
// Other week
var start = daysInFirstWeek + ((weekNumber - 2) * 7);
return Math.Max(0, Math.Min(7, duration - start));
}
This should do what you want.
public static int DaysInWeek(DateTime arrivalDate, int weekNumber, int duration)
{
const int daysInAWeek = 7;
//get the value of Saturday (your week start day)
int firstDayOfWeekIndex = (int)DayOfWeek.Saturday;
//get the day of week of the first day
int startDay = arrivalDate.DayOfWeek;
//Find out number of says until next Saturday (days in first week)
int daysInFirstWeek = (startDay + firstDayOfWeekIndex) % daysInAWeek;
//Get the 'Full Weeks', that have all 7 days in the duration
int fullWeeks = (duration - daysInFirstWeek) / daysInAWeek;
//Get any leftover days
int leftover = duration - daysInAWeek * fullWeeks - daysInFirstWeek;
//Get total number of weeks (complete or otherwise)
int totalWeeks = 1 + fullWeeks + (leftover > 0 ? 1 : 0);
//return accordingly
if(weekNumber > totalWeeks)
return 0;
else if(weekNumber == 1)
return daysInFirstWeek;
else if(weekNumber == totalWeeks)
return daysInLastWeek;
else return daysInAWeek;
}
No list, no iteration, no recursion needed.
Hope this helps. Cheers.
My solution:
public static int DaysInWeekNew(DateTime arrivalDate, int weekNumber, int duration)
{
var offset = DayOfWeek.Saturday - arrivalDate.DayOfWeek;
//First Week
if (offset == 0) offset = 7; //if startdays is saturday
if (weekNumber == 1) return offset > duration ? duration : offset;
int numberofmiddleweeks = (duration - offset) / 7; //floor
//middleweeks
if (weekNumber - 1 <= numberofmiddleweeks) return 7;//-1 for the first week
//Last Week
return duration - offset - numberofmiddleweeks * 7;
}
EDIT: updated for startday saturday
This is a shorter version that satisfies your 3 test cases. No recursion needed. Basically the result is going to fall into 1 of 3 outcomes. The results are always going to be either part of the first week, the whole of a middle week or part of the last week.
This code deals with the three senarios.
public static int DaysInWeekNew(DateTime arrivalDate, int weekNumber, int duration)
{
var offset = arrivalDate.DayOfWeek == DayOfWeek.Saturday ? 7 : DayOfWeek.Saturday - arrivalDate.DayOfWeek;
//First Week
if (duration < offset) return duration;
if (weekNumber == 1) return offset;
//Middle Week
var start = offset + ((weekNumber - 2) * 7);
if (start + 7 < duration) return 7;
//Last Week
return duration - start;
}
UPDATED based on comments
As the title says, given the year and the week number, how do I get the month number?
edit: if a week crosses two months, I want the month the first day of the week is in.
edit(2): This is how I get the week number:
CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(DateTime.Now, CalendarWeekRule.FirstDay, DayOfWeek.Monday);
I'm just trying to do the reverse.
If you assume that the first day of your definition of week is the same day as the 1st day of the year, then this will work:
int year = 2000;
int week = 9;
int month = new DateTime(year, 1, 1).AddDays(7 * (week - 1)).Month;
Obviously, a true answer would depend on how you define the first day of the week, and how you define how a week falls into a month when it overlaps more than one.
This is what I ended up doing:
static int GetMonth(int Year, int Week)
{
DateTime tDt = new DateTime(Year, 1, 1);
tDt.AddDays((Week - 1) * 7);
for (int i = 0; i <= 365; ++i)
{
int tWeek = CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(
tDt,
CalendarWeekRule.FirstDay,
DayOfWeek.Monday);
if (tWeek == Week)
return tDt.Month;
tDt = tDt.AddDays(1);
}
return 0;
}
I would have preferred something simpler, but it works :)
Wouldn't it also depend on the day of the week?
this should be able to help
public int getMonth(int weekNum, int year)
{
DateTime Current = new DateTime(year, 1, 1);
System.DayOfWeek StartDOW = Current.DayOfWeek;
int DayOfYear = (weekNum * 7) - 6; //1st day of the week
if (StartDOW != System.DayOfWeek.Sunday) //means that last week of last year's month
{
Current = Current.AddDays(7 - (int)Current.DayOfWeek);
}
return Current.AddDays(DayOfYear).Month;
}
Another problem you could face is that most years do not start at the beginning of a week, which shifts everything.
Assumptions:
Sunday is the first day of the week.
Partial week still counts as week 1
Outputs beginning and ending month as integer array.
public int[] getMonth(int weekNum, int year)
{
DateTime StartYear = new DateTime(year, 1, 1);
System.DayOfWeek StartDOW = StartYear.DayOfWeek;
DateTime DayOfYearWeekStart = default(DateTime);
DateTime DayOfYearWeekEnd = default(DateTime);
int x = 0;
if ((StartDOW == System.DayOfWeek.Sunday)) {
DayOfYearWeekStart = StartYear.AddDays((weekNum - 1) * 7);
DayOfYearWeekEnd = DayOfYearWeekStart.AddDays(6);
} else {
for (x = 0; x <= 7; x += 1) {
if (StartYear.AddDays(x).DayOfWeek == DayOfWeek.Sunday) {
break; // TODO: might not be correct. Was : Exit For
}
}
if (weekNum == 1) {
DayOfYearWeekStart = StartYear;
DayOfYearWeekEnd = StartYear.AddDays(x - 1);
} else if (weekNum > 1) {
DayOfYearWeekStart = StartYear.AddDays(((weekNum - 2) * 7) + x);
DayOfYearWeekEnd = DayOfYearWeekStart.AddDays(6);
}
}
int[] Month = new int[2];
Month[0] = DayOfYearWeekStart.Month;
Month[1] = DayOfYearWeekEnd.Month;
return Month;
}
You cant. You need at least the day on which the 1st week starts (or when the week starts), to get an accurate answer.
You cant. A week may start in one month and end in another.
I think you're assuming that a "week" is any group of 7 sequential days. It isn't. Given Year(2008), Week(5), you could be in either January or Febuary, depending on when your "week" starts.
In .NET 3.0 and later you can use the ISOWeek-Class.
public static int MonthOfFirstDay(int year, int week)
{
return ISOWeek.ToDateTime(year, week, DayOfWeek.Monday).Month;
}
Note that the year might not fit, as the first week of a year can already start in end of December the year before. For instance the first week of 2020 started on Monday 2019-12-30.
// Calculate the week number according to ISO 8601
public static int Iso8601WeekNumber(DateTime dt)
{
return CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(dt, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
}
// ...
DateTime dt = DateTime.Now;
// Calculate the WeekOfMonth according to ISO 8601
int weekOfMonth = Iso8601WeekNumber(dt) - Iso8601WeekNumber(dt.AddDays(1 - dt.Day)) + 1;