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;
}
}
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);
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.
Let's say I need to find out when the next scheduled date is when I know that the schedule was based off a start date of 8/1/2014, it's supposed to run every 7 days and the current date is 8/10/2014. I should get back a date of 8/14/2014. I eventually want to make this code work for every X hours, days, and weeks, bur right now I'm just testing with days. I have the following code I'm using to calculate the next run time, but I get it to work for one date and then it fails for another. FYI, I'm using the option to specify the current date for testing purposes. What am I doing wrong?
public class ScheduleComputer
{
public DateTime GetNextRunTime(ScheduleRequest request)
{
var daysSinceBase = ((int)((request.CurrentDate - request.BaseDate).TotalDays)) + 1;
var partialIntervalsSinceBaseDate = daysSinceBase % request.Interval;
var fullIntervalsSinceBaseDate = daysSinceBase / request.Interval;
var daysToNextRun = 0;
if (partialIntervalsSinceBaseDate > 0)
{
daysToNextRun = (request.Interval - partialIntervalsSinceBaseDate) + 1;
}
var nextRunDate = request.BaseDate.AddDays((fullIntervalsSinceBaseDate * request.Interval) + daysToNextRun - 1);
return nextRunDate;
}
}
public class ScheduleRequest
{
private readonly DateTime _currentDate;
public ScheduleRequest()
{
_currentDate = DateTime.Now;
}
public ScheduleRequest(DateTime currentDate)
{
_currentDate = currentDate;
}
public DateTime CurrentDate
{
get { return _currentDate; }
}
public DateTime BaseDate { get; set; }
public Schedule Schedule { get; set; }
public int Interval { get; set; }
}
public enum Schedule
{
Hourly,
Daily,
Weekly
}
And here are my unit tests
[TestFixture]
public class ScheduleComputerTests
{
private ScheduleComputer _scheduleComputer;
[SetUp]
public void SetUp()
{
_scheduleComputer = new ScheduleComputer();
}
[Test]
public void ThisTestPassesAndItShould()
{
var scheduleRequest = new ScheduleRequest(currentDate: DateTime.Parse("8/14/2014"))
{
BaseDate = DateTime.Parse("8/1/2014"),
Schedule = Schedule.Daily,
Interval = 7
};
var result = _scheduleComputer.GetNextRunTime(scheduleRequest);
Assert.AreEqual(DateTime.Parse("8/14/2014"), result);
}
[Test]
public void ThisTestFailsAndItShouldNot()
{
var scheduleRequest = new ScheduleRequest(currentDate: DateTime.Parse("8/2/2014"))
{
BaseDate = DateTime.Parse("8/1/2014"),
Schedule = Schedule.Daily,
Interval = 7
};
var result = _scheduleComputer.GetNextRunTime(scheduleRequest);
Assert.AreEqual(DateTime.Parse("8/7/2014"), result);
}
FYI, I saw the post here, but I can't seem to tailor it to my needs.
--- UPDATE 1 ---
Here is my updated code. I know I've made it verbose with variables so I can understand the logic better (hopefully that doesn't impact performance much). I also added logic to deal with different periods (hours, days, weeks) and added extension methods to make the code somewhat cleaner. However, this code seems to be working perfectly for hours and days, but is failing on weeks. Somewhere I'm not multiplying or dividing by 7 properly.
public class ScheduleComputer
{
public DateTime GetNextRunTime(ScheduleRequest request)
{
var timeBetwenCurrentAndBase = request.CurrentDate - request.BaseDate;
var totalPeriodsBetwenCurrentAndBase = timeBetwenCurrentAndBase.TotalPeriods(request.Schedule);
var fractionalIntervals = totalPeriodsBetwenCurrentAndBase % request.Interval;
var partialIntervalsLeft = request.Interval - fractionalIntervals;
if (request.Schedule != Schedule.Hourly) partialIntervalsLeft = partialIntervalsLeft - 1;
var nextRunTime = request.CurrentDate.AddPeriods(partialIntervalsLeft, request.Schedule);
return nextRunTime;
}
}
public static class ScheduleComputerExtensions
{
public static double TotalPeriods(this TimeSpan timeBetwenCurrentAndBase, Schedule schedule)
{
switch (schedule)
{
case Schedule.Hourly: return timeBetwenCurrentAndBase.TotalHours;
case Schedule.Daily: return timeBetwenCurrentAndBase.TotalDays;
case Schedule.Weekly: return timeBetwenCurrentAndBase.TotalDays * 7;
default: throw new ApplicationException("Invalid Schedule Provided");
}
}
public static DateTime AddPeriods(this DateTime dateTime, double partialIntervalsLeft, Schedule schedule)
{
switch (schedule)
{
case Schedule.Hourly: return dateTime.AddHours(partialIntervalsLeft);
case Schedule.Daily: return dateTime.AddDays(partialIntervalsLeft);
case Schedule.Weekly: return dateTime.AddDays(partialIntervalsLeft * 7);
default: throw new ApplicationException("Invalid Schedule Provided");
}
}
}
Try replacing your GetNextRunTime with this
public DateTime GetNextRunTime(ScheduleRequest request)
{
double days = (request.Interval - ((request.CurrentDate - request.BaseDate).TotalDays % request.Interval));
return request.CurrentDate.AddDays(days-1);
}
That should give you the correct dates.
EDIT: Let's break it down in hopes of helping you figure out the logic.
diff = (request.CurrentDate - request.BaseDate).TotalDays this gives you the number of days between the BaseDate and CurrentDate. Note that the number of days DOES NOT INCLUDE the day for BaseDate. So the different between 8/7/14 and 8/1/14 is 6 days.
daysSinceLast = diff % request.Interval this gives you the number of days that have past since the last interval hit, so if the last interval hit on 8/1/14 and it is now 8/7/14 then the result would be 6 % 7 = 6; 6 days have past since the last scheduled interval (not including the last interval date). This is the most important part of the calculation; It keeps the number of days, no matter how many have passed within the interval, so for example, if 100 days have passed since the BaseDate and the interval is 7: 100 % 7 = 2 which means that 2 days have passed since the last interval triggered, there is no need to actually know the last date it was triggered. All you need is the BaseDate and CurrentDate. You could use this logic to find the date of the last triggered interval, just subtract the number of days from the CurrentDate.
daysUntil = request.Interval - daysSinceLast This gives you the number of days until the next scheduled interval. 7 - 6 = 1 day until the next scheduled interval
1 day in this scenario is not correct and the result will never be correct because the calculation of TimeSpan differences does not include the day for BaseDate, so you need to subtract 1 from the daysUntil nextDate = request.CurrentDate.AddDays(daysUntil - 1)
Adding the number of remaining days (minus 1 for the base date) to the current date gives you the required value. Does this help at all?
UPDATE
In light of your testing, I see that the problem is on both of our ends. My calculation was incorrect and you were multiplying by 7 when you needed to divide by 7. Either way, the result was still wrong. Try this instead.
Remove your extension class completely
Modify your GetNextRunTime with the below code
Modify your ScheduleRequest and Schedule class/enum with the below code
GetNextRunTime
public DateTime GetNextRunTime(ScheduleRequest request)
{
double diffMillis = (request.CurrentDate - request.BaseDate).TotalMilliseconds;
double modMillis = (diffMillis % request.IntervalMillis);
double timeLeft = (request.IntervalMillis - modMillis);
ulong adjust = (request.Schedule == Schedule.Daily) ? (ulong)Schedule.Daily : 0;
return request.CurrentDate.AddMilliseconds(timeLeft - adjust);
}
ScheduleRequest
public class ScheduleRequest
{
private readonly DateTime _currentDate;
public ScheduleRequest()
{
_currentDate = DateTime.Now;
}
public ScheduleRequest(DateTime currentDate)
{
_currentDate = currentDate;
}
public DateTime CurrentDate
{
get { return _currentDate; }
}
public DateTime BaseDate { get; set; }
public Schedule Schedule { get; set; }
public double IntervalMillis { get { return (double)this.Schedule * this.Interval; } }
public int Interval { get; set; }
}
Schedule
public enum Schedule : ulong
{
Hourly = 3600000,
Daily = 86400000,
Weekly = 604800000
}
This should work correctly for all dates, intervals and schedules. EDIT: corrected adjust value
Suppose night time is set from 20.30h till 6.15h(AM). These 2 parameters are user-scoped variables.
Suppose you have an arrival date and a departure date which can span from a few minutes to more than one total day.
How do you calculate the total hours of night time?
public static double CalculateTotalNightTimeHours(DateTime arrival,
DateTime departure,
int nightTimeStartHour,
int nightTimeStartMinute,
int nightTimeEndHour,
int nightTimeEndMinute)
{
//??
}
EDIT: I understand this may be no straight forward yes/no answer, but maybe someone has an elegant solution for this problem.
To answer the comments : I indeed want to calculate the total number of hours (or minutes) that fall between a user-editable night start and end time. I'm calculating visit time, and the first date is indeed the arrival parameter.
The code I had sofar :
DateTime nightStart = new DateTime( departure.Year, departure.Month, departure.Day,
nightTimeStartHour, nightTimeStartMinute, 0);
DateTime nightEnd = new DateTime( arrival.Year, arrival.Month, arrival.Day,
nightTimeEndHour, nightTimeEndMinute, 0);
if (arrival < nightEnd)
{
decimal totalHoursNight = (decimal)nightEnd.Subtract(arrival).TotalHours;
}
//...
Just because I was up for the challenge you should be able to use the following function with success. Please note that this is probably not the most efficient way to do it, but I did it this way so I could lay out the logic. I may decide to edit this as some point to improve it, but it should work fine as is.
It is also important to note a couple of assumptions here:
the 'end' parameter is always greater than the 'start' parameter (although we check that first thing anyway)
the night end parameters are earlier than the night start parameters (i.e. night time ends on the following day, but never as much as 24 hours later)
Daylight savings time does not exist! (this is a tricky concern, one important question to address is: if either your start or end time is at 01:30 on the day the clocks go back, how will you know if the time was recorded before or after the rollback? i.e is it the first or second time the clock has hit 01:30?)
with that in mind...
public static double Calc(DateTime start, DateTime end, int startHour, int startMin, int endHour, int endMin)
{
if (start > end)
throw new Exception();//or whatever you want to do
//create timespans for night hours
TimeSpan nightStart = new TimeSpan(startHour, startMin, 0);
TimeSpan nightEnd = new TimeSpan(endHour, endMin, 0);
//check to see if any overlapping actually happens
if (start.Date == end.Date && start.TimeOfDay >= nightEnd && end.TimeOfDay <= nightStart)
{
//no overlapping occurs so return 0
return 0;
}
//check if same day as will process this differently
if (start.Date == end.Date)
{
if (start.TimeOfDay > nightStart || end.TimeOfDay < nightEnd)
{
return (end - start).TotalHours;
}
double total = 0;
if (start.TimeOfDay < nightEnd)
{
total += (nightEnd - start.TimeOfDay).TotalHours;
}
if(end.TimeOfDay > nightStart)
{
total += (end.TimeOfDay - nightStart).TotalHours;
}
return total;
}
else//spans multiple days
{
double total = 0;
//add up first day
if (start.TimeOfDay < nightEnd)
{
total += (nightEnd - start.TimeOfDay).TotalHours;
}
if (start.TimeOfDay < nightStart)
{
total += ((new TimeSpan(24, 0, 0)) - nightStart).TotalHours;
}
else
{
total += ((new TimeSpan(24, 0, 0)) - start.TimeOfDay).TotalHours;
}
//add up the last day
if (end.TimeOfDay > nightStart)
{
total += (end.TimeOfDay - nightStart).TotalHours;
}
if (end.TimeOfDay > nightEnd)
{
total += nightEnd.TotalHours;
}
else
{
total += end.TimeOfDay.TotalHours;
}
//add up any full days
int numberOfFullDays = (end - start).Days;
if (end.TimeOfDay > start.TimeOfDay)
{
numberOfFullDays--;
}
if (numberOfFullDays > 0)
{
double hoursInFullDay = ((new TimeSpan(24, 0, 0)) - nightStart).TotalHours + nightEnd.TotalHours;
total += hoursInFullDay * numberOfFullDays;
}
return total;
}
}
You can then call it something like this:
double result = Calc(startDateTime, endDateTime, 20, 30, 6, 15);
Basically you'll want to calculate when night starts and ends. Then compare those to the arrival and departure dates to see if you arrival after night starts or depart before it ends to get the values you need to subtract to determine the total night hours. Then you need to continue to calculate this for each day until the start time for night is pass the departure date. Here's my solution for that.
public static double CalculateTotalNightTimeHours(
DateTime arrival,
DateTime departure,
int nightTimeStartHour,
int nightTimeStartMinute,
int nightTimeEndHour,
int nightTimeEndMinute)
{
if (arrival >= departure)
return 0;
var nightStart = arrival.Date.AddHours(nightTimeStartHour).AddMinutes(nightTimeStartMinute);
var nightEnd = nightStart.Date.AddDays(1).AddHours(nightTimeEndHour).AddMinutes(nightTimeEndMinute);
double nightHours = 0;
while (departure > nightStart)
{
if (nightStart < arrival)
nightStart = arrival;
if (departure < nightEnd)
nightEnd = departure;
nightHours += (nightEnd - nightStart).TotalHours;
nightStart = nightStart.Date.AddDays(1).AddHours(nightTimeStartHour).AddMinutes(nightTimeStartMinute);
nightEnd = nightStart.Date.AddDays(1).AddHours(nightTimeEndHour).AddMinutes(nightTimeEndMinute);
}
return nightHours;
}
You'd probably also want to add checking to make sure the start and end hours are within range. This also assumes that night starts on one day and ends on the next, so if you wanted night to end before midnight you'd have to do something else.
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;
}