This question already has answers here:
AddBusinessDays and GetBusinessDays
(17 answers)
Closed 3 years ago.
I am trying to calculate a finishing date when adding a duration to a start date, but skipping weekends and holidays:
DateTime start = DateTime.Now;
TimeSpan duration = TimeSpan.FromHours(100);
List<DateTime> = //List of holidays
DateTime end = ?
For example if it is 11pm on a Friday and I add 2 hours, it would end on 1am Monday morning.
Is there a neat way of doing this?
I have a temporary fix which increments the time by an hour and checks the day of the week, but it is very inefficient.
Original Idea (untested):
public static DateTime calEndDate(DateTime start, TimeSpan duration, List<DateTime> holidays)
{
var startDay = start.Day;
var i = 0;
var t = 0;
while (TimeSpan.FromHours(t) < duration)
{
var date = start.Add(TimeSpan.FromHours(i));
if (date.DayOfWeek.ToString() != "Saturday" && date.DayOfWeek.ToString() != "Sunday") //and something like !holidays.contains(start)
{
t++;
}
i++;
}
return start.Add(TimeSpan.FromHours(t));
}
}
However is needs to run over 100 times for different start dates/durations on one asp.net page load. I don't know how to benchmark it, but it doesn't seem like an elegant solution?
Here's an algorithm I'd try.
I'm on my phone, and I'll get it wrong, but you should see the logic...
var end = start;
var timeToMidnight = start.Date.AddDays(1) -start;
if ( duration < timeToMidnight ) return start + duration;
end = endMoment + timeToMidnight;
duration = duration - timeToMidnight;
//Helper method
bool IsLeisure(Datetime dt) => (dt.DayOfWeek == DayOfWeek.Saturday) || (dt.DayOfWeek == DayOfWeek.Sunday) || holidays.Any( h => h.Date == dt.Date);
//We're at the first tick of the new day. Let's move to a work day, if needed.
while(IsLeisure(end)) { end = end.AddDays(1); };
//Now let's process full days of 'duration'
while(duration >= TimeSpan.FromDays(1) ) {
end = end.AddDays(1);
if(!IsLeisure(end)) duration = duration - TimeSpan.FromDays(1);
}
//Finally, add the reminder
end = end + duration;
Note: you haven't specified the logic for when start moment is a weekend or a holiday.
Yes there is :
DateTime currentT = DateTime.Now;
DateTime _time_ = currentTime.AddHours(10);
Simple and neat.
Related
I have been looking at Microsoft's documents and many stack overflow posts but none seem to answer my question. I want to know the simplest and easiest way to get an accurate week number for the current date in c#. I am pretty new to c# so please try and keep it simple. I have tried using:
int week = DateTime.Now.DayOfYear/7;
Console.WriteLine(week)
but on Monday (when I would like it to move onto the next week) it would show as the previous week.
Eg: If the date was 21/12/2020 it would say the current week is the 50th, which is 2 weeks off. Then on 22/12/2020 it would say it is the 51st week, which is 1 week off.
Please Help & Thanks in advance.
This is probably what you are looking for:
DateTime dt = new DateTime(2020, 12, 21);
Calendar cal = new CultureInfo("en-US").Calendar;
int week = cal.GetWeekOfYear(dt, CalendarWeekRule.FirstDay, DayOfWeek.Monday);
Console.WriteLine(week);
You can change the CalendarWeekRule parameter to change the definition of the first week of the year:
FirstDay means that first week of the year can have any length. For example if the first day of the year was Sunday, it will be counted as week and the following Monday will be counted as part of second week.
FirstFourDayWeek means that the first week will be counted only if it mainly in this year. For example if the first day of the year will be Thursday the week will be counted, but if the year starts with Friday, the first week won't be counted.
FirstFullWeek means that the first week that will be counted will be the first full week of the year.
I have looked at this as well when I was writing an application in LotusNotes. From what I have found, the first week of the year must contain a Thursday. If you assume that Sunday is the last day of the week, then the lowest date for Sunday has to be the 4th. With this is mind (and I am very new to C# and all the intricacies) I wrote this code which will give you the week number of any given date and also the number of weeks for this year and the previous. #
public class DateCalculations
{
private readonly DateTime _weekDate;
private DateTime ThisSunday => GetSundayDate(_weekDate);
private DateTime FirstDay_ThisYear => DateTime.Parse($"01/01/{ ThisSunday.Year }");
private DateTime FirstDay_LastYear => DateTime.Parse($"01/01/{ ThisSunday.Year - 1 }");
private DateTime FirstDay_NextYear => DateTime.Parse($"01/01/{ ThisSunday.Year + 1 }");
private DateTime FirstSunday_ThisYear => GetSundayDate_WeekOne(FirstDay_ThisYear);
private DateTime FirstSunday_LastYear => GetSundayDate_WeekOne(FirstDay_LastYear);
private DateTime FirstSunday_NextYear => GetSundayDate_WeekOne(FirstDay_NextYear);
public DateCalculations(string weekDate)
{
if (DateTime.TryParse(weekDate, out _weekDate))
{
return;
}
else
{
throw new Exception("Incorrect date has been supplied");
}
}
private bool IsDateInFirstWeek(DateTime suppliedDate)
{
var output = false;
// First week must contain a Thursday, so lowest Sunday date possible is the 4th
if (suppliedDate.Day >= 4)
{
output = true;
}
return output;
}
private DateTime GetSundayDate(DateTime suppliedDate)
{
var checkDay = suppliedDate;
//Check if the day of the supplied date is a Sunday
while (checkDay.DayOfWeek != DayOfWeek.Sunday)
{
checkDay = checkDay.AddDays(1);
}
return checkDay;
}
private DateTime GetSundayDate_WeekOne(DateTime suppliedDate)
{
var checkDay = GetSundayDate(suppliedDate);
if (IsDateInFirstWeek(checkDay) == false)
{
checkDay = checkDay.AddDays(7);
}
return checkDay;
}
public int WeekNumber()
{
var output = 0;
if (ThisSunday == FirstSunday_ThisYear)
{
output = 1;
}
else if(ThisSunday > FirstSunday_ThisYear)
{
TimeSpan daysBetween = ThisSunday - FirstSunday_ThisYear;
output = (daysBetween.Days/7) + 1;
}
else
{
TimeSpan daysBetween = ThisSunday - FirstSunday_LastYear;
output = (daysBetween.Days / 7) + 1;
}
return output;
}
public int TotalWeeksThisYear()
{
TimeSpan daysBetween = FirstSunday_NextYear - FirstSunday_ThisYear;
return (daysBetween.Days / 7);
}
public int TotalWeeksLastYear()
{
TimeSpan daysBetween = FirstSunday_ThisYear - FirstSunday_LastYear;
return (daysBetween.Days / 7);
}
}
My console was used to test
class Program
{
static void Main()
{
var test = new DateCalculations("2021-01-03");
var weekNumber = test.WeekNumber();
var totalWeeks = test.TotalWeeksThisYear();
var pastWeeks = test.TotalWeeksLastYear();
Console.ReadLine();
}
}
The date format can be any string representation of a date (English or American)
Hope this helps. It may need refactoring though :)
Built on top of this answer: by #bunny4
But not everyone is located in the US or might have to support several cultures.
Use this solution to support a cultural defined week rule and first-Day rule.. e.g. Denmark has "FirstFourDayWeek" rule for weeks and "Monday" as first day of the week.
//for now, take the the current executing thread's Culture
var cultureInfo = Thread.CurrentThread.CurrentCulture;
//let's pick a date
DateTime dt = new DateTime(2020, 12, 21);
DayOfWeek firstDay = cultureInfo.DateTimeFormat.FirstDayOfWeek;
CalendarWeekRule weekRule = cultureInfo.DateTimeFormat.CalendarWeekRule;
Calendar cal = cultureInfo.Calendar;
int week = cal.GetWeekOfYear(dt, weekRule, firstDay);
Consider the following object:
new TimeObject
{
StartTime = new DateTime(2019, 1, 1, 0, 0 , 0),
DurationInMinutes = 20,
RepeatFrequencyType = RepeatFrequencyType.Month
//This would mean repeat every 1 month.
RepeatFrequency = 1,
}
I need to write code that will show a message on screen on the 1st of January 2019 and then repeat each month at same time. Now this is displayed on a website so you could load the page halfway through the message having to show up. So to solve this I thought of a two step process. First is to find what the next start time is (And this could be in the past if the user loads while the message is shown) and then step 2 is to figure out if I should show the message or not or how long until I need to show it. Step 2 is easy to solve, but step one is the trouble some. As such here is my solution which works with unit tests I have setup.
Please note that I am here showing code only for step 1 and that is the part I need help with. I explained the full picture for you to better understand the problem.
private DateTime GetNextMonthStartDate()
{
var currentDate = DateTime.UtcNow;
//If this is the first time it is running then we just return the initial start time.
if (currentDate < StartTime.AddMinutes(DurationInMinutes)) return StartTime;
//If we happen to run this when there is 0 minutes left, then return next month'start time.
if (currentDate == StartTime.AddMinutes(DurationInMinutes)) return StartTime.AddMonths(RepeatFrequency);
var dayOfTheMonth = StartTime.Day;
var previousDateOfTheMonth = currentDate.AddMinutes(-DurationInMinutes);
//As not every month has same number of days, if we are on one which has less days then we just run it on the last day of that month.
var lastDay = DateTime.DaysInMonth(currentDate.Year, currentDate.Month);
if (dayOfTheMonth > lastDay) dayOfTheMonth = lastDay;
//If on the same day
if (currentDate.Day == dayOfTheMonth)
{
var nextStartDate = new DateTime(currentDate.Year, currentDate.Month, currentDate.Day, StartTime.Hour, StartTime.Minute, StartTime.Second);
var endDate = nextStartDate.AddMinutes(DurationInMinutes);
//If the event is still lasting or has not started then return current date and start time else return next month start time.
return currentDate < endDate ? nextStartDate : nextStartDate.AddMonths(RepeatFrequency);
}
//If the event is still running but it started in previous day, we return start date of that previous day.
if (currentDate.Day != previousDateOfTheMonth.Day && previousDateOfTheMonth.Day == dayOfTheMonth)
{
return new DateTime(previousDateOfTheMonth.Year, previousDateOfTheMonth.Month, previousDateOfTheMonth.Day, StartTime.Hour, StartTime.Minute, StartTime.Second);
}
//Subtract next day of the month (based on the current year and month and start date) from the current date
var nextDayOfTheMonthDate = new DateTime(currentDate.Year, currentDate.Month, StartTime.Day);
var currentDateWithoutTime = new DateTime(currentDate.Year, currentDate.Month, currentDate.Day);
var daysUntilDayOfTheMonth = nextDayOfTheMonthDate.Subtract(currentDateWithoutTime).TotalDays;
//If days is less than 0 it means it has passed, so we will recalculate from the next month.
if (daysUntilDayOfTheMonth < 0)
{
daysUntilDayOfTheMonth = nextDayOfTheMonthDate.AddMonths(RepeatFrequency).Subtract(currentDateWithoutTime).TotalDays;
}
//Get the next day, month and year by adding days from current time. This will ensure things like switching into next year won't cause a problem.
var nextDate = currentDate.AddDays(daysUntilDayOfTheMonth);
//return date time with nextDate year, month and day with startDate time.
return new DateTime(nextDate.Year, nextDate.Month, nextDate.Day, StartTime.Hour, StartTime.Minute, StartTime.Second);
}
As you can see it feels somewhat complicated and now I need to this for Year frequency, Day, Hour, etc... I am wondering if there is simpler logic to accomplish this or potentially code built in the framework I could use to figure this out?
If I understand the question correctly, you're trying to add RepeatFrequency to StartTime until you've reached a DateTime value that is greater than the current date.
If this is the case, I think you can just use a loop where you increment the nextTime by RepeatFrequency until nextTime > DateTime.UtcNow.
First, I'm making an assumption that you have an enum something like the following:
enum RepeatFrequencyType
{
Minutes,
Hours,
Days,
Weeks,
Months,
Years,
FirstWeekdayOfMonth
}
If so, I think this logic may solve the issue:
class TimeObject
{
public DateTime StartTime { get; set; }
public int DurationInMinutes { get; set; }
public RepeatFrequencyType RepeatFrequencyType { get; set; }
public int RepeatFrequency { get; set; }
public DateTime NextStartTime()
{
var currentTime = DateTime.UtcNow;
// Grab the StartTime and add the duration
var nextTime = StartTime.AddMinutes(DurationInMinutes);
// Continue to increment it until it's greater than the current time
while (currentTime >= nextTime)
{
switch (RepeatFrequencyType)
{
case RepeatFrequencyType.Minutes:
nextTime = nextTime.AddMinutes(RepeatFrequency);
break;
case RepeatFrequencyType.Hours:
nextTime = nextTime.AddHours(RepeatFrequency);
break;
case RepeatFrequencyType.Days:
nextTime = nextTime.AddDays(RepeatFrequency);
break;
case RepeatFrequencyType.Weeks:
nextTime = nextTime.AddDays(RepeatFrequency * 7);
break;
case RepeatFrequencyType.Months:
nextTime = nextTime.AddMonths(RepeatFrequency);
break;
case RepeatFrequencyType.Years:
nextTime = nextTime.AddYears(RepeatFrequency);
break;
case RepeatFrequencyType.FirstWeekdayOfMonth:
nextTime = GetNextFirstWeekdayOfMonth(nextTime.AddMonths(RepeatFrequency));
break;
default:
throw new Exception("Unknown value for RepeatFrequency specified.");
}
}
// Remove the added duration from the return value
return nextTime.AddMinutes(-DurationInMinutes);
}
private DateTime GetNextFirstWeekdayOfMonth(DateTime date)
{
// Start at the first day of the month
var firstWeekday = new DateTime(date.Year, date.Month, 1);
// While the first day is not a weekday, add a day
while (firstWeekday.DayOfWeek == DayOfWeek.Saturday ||
firstWeekday.DayOfWeek == DayOfWeek.Saturday)
{
firstWeekday.AddDays(1);
}
// If the specified date is greater than the first weekday,
// return the first weekday of the next month.
if (date > firstWeekday)
{
firstWeekday = GetNextFirstWeekdayOfMonth(date.AddMonths(1));
}
return firstWeekday;
}
}
I have to add 15 minutes to the current time and set it to a DateTime object in C#. If my current time is say 11:50 PM, and 15 minutes is added, the hour part becomes 24 and is causing the following error: "Hour, Minute, and Second parameters describe an un-representable DateTime."
public static DateTime NewTime(this DateTime dateTime)
{
int hour = dateTime.Hour;
int minute = dateTime.Minute;
if (minute > 0)
{
minute = dateTime.Minute + (15);
if (minute >= 60)
{
hour = hour + 1;
minute = 0;
}
}
return new DateTime(dateTime.Year, dateTime.Month,
dateTime.Day, hour, minute, 0);
}
Thanks
Your logic does not make sense, you are only adding minutes if the minutes are greater than 0 so what happens if they are 0?
To add time use the methods built into the type definition, no need to reinvent the wheel. Example:
public static DateTime Add15Minutes(this DateTime dateTime)
{
return dateTime.AddMinutes(15);
}
You are checking for an overflow on the minute attribute, but not the hour attribute. You could check for an overflow on the hour attribute like this:
public static DateTime NewTime(this DateTime dateTime)
{
int hour = dateTime.Hour;
int minute = dateTime.Minute;
var day = dateTime.Day;
if (minute > 0)
{
minute = dateTime.Minute + (15);
if (minute >= 60)
{
hour = hour + 1;
minute = 0;
}
}
if (hour > 24) {
day += 1;
}
return new DateTime(dateTime.Year, dateTime.Month,
day, hour, minute, 0);
}
However, you will also run into problems with the overflow of days in a month, which is even more complicated to handle. Instead, just use the built in Add function:
public static DateTime NewTime(this DateTime dateTime)
{
return new dateTime.AddMinutes(15);
}
I think you are overthinking this maybe? DateTime already provides many support methods and this will probably do what you need without the need to create an extension method:
var myValue = new DateTime(2017,3,14,23,50,0);
var result = myValue.AddMinutes(15);
I have my start date as 05/03/2012 and duration is 200 days now I would like to get the end date excluding sundays. So that my end date should be 05/02/2013.. Can some one help me
Try this for me:
var startDate = new DateTime(2012, 5, 3);
var sundaysOverDuration = 200 / 7;
var actualDuration = 200 + sundaysOverDuration;
var newDate = startDate.AddDays(actualDuration);
I also honestly have to admit that this link is flat out elegant surrounding how it handles a lot of the exceptions that exist when doing these types of calculations. I'm not sure you need something that complex, but it's worth letting you know. I'm going to inline the code just to ensure it's preserved if the link is ever broken.
public static double GetBusinessDays(DateTime startD, DateTime endD)
{
double calcBusinessDays =
1 + ((endD-startD).TotalDays * 6 -
(startD.DayOfWeek-endD.DayOfWeek) * 2) / 7;
if ((int)startD.DayOfWeek == 0) calcBusinessDays --;
return calcBusinessDays;
}
public static DateTime AddWorkDaysToStartDate(DateTime startD, double businessDays)
{
int DoW = (int)startD.DayOfWeek;
double temp = businessDays + DoW + 1;
if (DoW != 0) temp --;
DateTime calcendD = startD.AddDays(
Math.Floor(temp / 6)*2-DoW + temp
- 2* Convert.ToInt32(temp % 6 == 0)) ;
}
Finally, based on your question it doesn't appear you need to handle holidays, but if you do the solution is much more complex and would need to be database driven, so just keep that in mind.
You can use the CalendarDateAdd class from the Time Period Library for .NET:
// ----------------------------------------------------------------------
public void AddDaysSample()
{
CalendarDateAdd calendarDateAdd = new CalendarDateAdd();
calendarDateAdd.AddWorkingWeekDays();
calendarDateAdd.WeekDays.Add( DayOfWeek.Saturday );
DateTime start = new DateTime( 2012, 5, 3 );
TimeSpan duration = new TimeSpan( 200, 0, 0, 0 );
DateTime? end = calendarDateAdd.Add( start, duration );
Console.WriteLine( "AddDaysSample : {0:d} + {1} days = {2:d}", start, duration.Days, end );
} // AddDaysSample
Interesting issue I'm facing and I just can't come up with an algorim to calculate.
Basically, what I want is to calculate a DateTime based on DateTime.Now.AddMinutes() but the Adding of minutes should take into consideration Working Hours and weekends.
In other words, if the time is currently 16:50 and i add 20 minutes, the method should return a DateTime for tomorrow morning at 08:10 (if tomorrow is not a weekend day).
I've started with some logic, but it's not complete. Does anyone have a sample which can save me a few hours of coding? This is what i've got so far:
public DateTime CalculateSLAFromNow(int minutes)
{
DateTime now = DateTime.Now;
TimeSpan slatimeaddedon = CalculateToNextWeekDay(DateTime.Now);
TimeSpan finalMinutesAddedon = slatimeaddedon.Add(new TimeSpan(0, minutes, 0));
DateTime SLATime = DateTime.Now.AddMinutes(slatimeaddedon.TotalMinutes);
return SLATime;
}
private TimeSpan CalculateToNextWeekDay(DateTime dt)
{
//Calculate.
}
public static DateTime CalculateSLAFromNow(int minutes)
{
double days = (double)minutes / 540;
DateTime now = DateTime.Now;
DateTime later = now;
while (days >= 1)
{
later = later.AddDays(1);
if (later.DayOfWeek == DayOfWeek.Saturday)
{
later = later.AddDays(2);
}
days--;
}
days = days * 540;
later = later.AddMinutes(days);
if (later.Hour > 17)
{
later = later.AddHours(15);
}
if (later.DayOfWeek == DayOfWeek.Saturday)
{
later = later.AddDays(2);
}
else if(later.DayOfWeek == DayOfWeek.Sunday)
{
later = later.AddDays(1);
}
return later;
}
There now it accounts for any number of minutes added (not the prettiest code, but it works)
Ok. Friend of mine wrote the following which works 100%. Thanks J for this. Herewith the complete solution:
private static DateTime DoCalculation(DateTime startDate, int minutes)
{
if (startDate.DayOfWeek == DayOfWeek.Sunday)
{
// if the input date is a sunday, set the actual SLA start date to the following monday morning 7:00AM
startDate = startDate.AddHours(24);
startDate = new DateTime(startDate.Year, startDate.Month, startDate.Day, 7, 0, 0);
}
else if (startDate.DayOfWeek == DayOfWeek.Saturday)
{
// if the input date is a saturday, set the actual SLA start date to the following monday morning 7:00AM
startDate = startDate.AddHours(48);
startDate = new DateTime(startDate.Year, startDate.Month, startDate.Day, 7, 0, 0);
}
DateTime resultDate = startDate;
for (int i = 0; i < minutes; i++)
{
resultDate = resultDate.AddMinutes(1);
// it is 5PM and time to go home
if (resultDate.Hour >= 17)
{
// if tomorrow is saturday
if (resultDate.AddDays(1).DayOfWeek == DayOfWeek.Saturday)
{
//add 48 hours to get us through the whole weekend
resultDate = resultDate.AddHours(48);
}
// add 14 hours to get us to next morning
resultDate = resultDate.AddHours(14);
}
}
return resultDate;
}