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.
Related
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.
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);
Hi I was solving a problem to calculate some library fine based on difference in return date and due date in C#. Now there are some constraints to the problem like
if the return year is changed i.e. if the return year is greater than the due date calendar year then fine is 10000. e.g. due date "31/12/2015" and return date "01/01/2016" then also fine is 10000.
if the return month is changed then fine is 500 * number of months late.
if the return day is changed then fine is 15 * number of days late.
else fine is 0.
Now i wrote the function below:
static int CalcFine (int[] returnedOn, int[] dueOn) {
int returnD = returnedOn[0];
int returnM = returnedOn[1];
int returnY = returnedOn[2];
int dueD = dueOn[0];
int dueM = dueOn[1];
int dueY = dueOn[2];
if (returnY > dueY) {
return 10000;
} else if (returnY < dueY) {
return 0;
} else {
if (returnM > dueM) {
return (returnM - dueM) * 500;
} else if (returnM < dueM) {
return 0;
} else {
if (returnD > dueD) {
return (returnD - dueD) * 15;
} else {
return 0;
}
}
}
}
I read about the DateTime class in C# that has pretty neat functions that return the difference in two dates as total days, total minutes, etc. But given the constraint of Fine being different based on year, month and days, I am not sure if there is any other inbuilt function to solve the above problem. In short I am trying to find if there is another simple way to solve the above problem without using so many if-else's.
You can get the difference in days, hours or minutes.
DateTime fromdate = new DateTime(2012,1,1);
DateTime todate = DateTime.Now;
TimeSpan diff = todate - fromdate;
int differenceInDays = diff.Days;
If you want to try differently for your validations and business rules. Follow the below code
public double GetFineAmount(DateTime DueDate)
{
DateTime dt = DateTime.Now;
int yeardiff, monthdiff, daydiff;
yeardiff = dt.Year - DueDate.Year;
if (yeardiff > 0) return 10000;
monthdiff = dt.Month - DueDate.Month;
if (monthdiff > 0) return 500 * monthdiff;
daydiff = dt.Day - DueDate.Day;
if (daydiff > 0) return 15 * daydiff;
return 0;
}
Editted again.. changed string pattern. I guess I need some sleep...
static int CalcFine (string returnedOn, string dueOn)
{
DateTime returnedDate = DateTime.ParseExact(
returnedOn, "d M yyyy", CultureInfo.InvariantCulture);
DateTime dueDate = DateTime.ParseExact(
dueOn, "d M yyyy", CultureInfo.InvariantCulture);
if (returnedDate < dueDate)
return 0;
if (returnedDate.Year > dueDate.Year)
return 10000;
if (returnedDate.Month > dueDate.Month)
return 500 * (returnedDate.Month - dueDate.Month);
if (returnedDate.Day > dueDate.Day)
return 15 * (returnedDate.Day - dueDate.Day);
else
return 0;
}
DateTime is a powerful tool. But you don't want to over-complicate this.
If you just find the difference between the two dates in days, the equation becomes a lot easier to manage versus trying to subtract dates.
static int CalcFine(DateTime returnedOn, DateTime dueOn)
{
TimeSpan dateDiff = (returnedOn - dueOn);
int TotalDays = dateDiff.Days;
if (TotalDays >= 365)
{
return 10000;
}
else if(TotalDays < 365 && TotalDays > 30 && TotalDays % 30 > 1)
{
return (500 * (TotalDays % 30));
}
else if(TotalDays < 30 && TotalDays > 0)
{
return 15 * TotalDays;
}
else
{
return 0;
}
}
Let's say we're tracking the times when a user is performing a certain action, and we want to know the average time between said actions.
For example, if the user performed this action at these times:
today, 1 PM
today, 3 PM
today, 6 PM
The result would be 2.5 hours.
I actually have solved this already, but I felt my solution was more complicated than necessary. I'll post it as an answer.
It seems that you are basically looking for Max - Min divided by Count.
public TimeSpan? Average
{
get
{
var diff = _dateTimes.Max().Subtract(_dateTimes.Min());
var avgTs = TimeSpan.FromMilliseconds(diff.TotalMilliseconds / (_dateTimes.Count() - 1));
return avgTs;
}
}
Make sure you check that there is more than one DateTime.
Update: Even more accurate if you use Ticks.
TimeSpan.FromTicks(diff.Ticks / (_dateTimes.Count() - 1));
I recently had a similar task in where I had a long running operation iterating over thousands of rows with 20-30 iterations within each.
void LongRunningOperation()
{
int r = 5000;
int sR = 20;
List<TimeSpan> timeSpanList = new List<TimeSpan>();
for (int i = 0; i < r; i++)
{
DateTime n = DateTime.Now; // Gets start time of this iteration.
for (int x = 0; x < sR; x++)
{
// DOING WORK HERE
}
timeSpanList.Add(DateTime.Now - n); // Gets the length of time of iteration and adds it to list.
double avg = timeSpanList.Select(x => x.TotalSeconds).Average(); // Use LINQ to get an average of the TimeSpan durations.
TimeSpan timeRemaining = DateTime.Now.AddSeconds((r - i) * avg) - DateTime.Now;
// Calculate time remaining by taking the total number of rows minus the number of rows done multiplied by the average duration.
UpdateStatusLabel(timeRemaining);
}
}
This is how I solved it, but I don't like it much:
public class HistoryItem
{
private IEnumerable<DateTime> _dateTimes;
public TimeSpan? Average
{
get {
TimeSpan total = default(TimeSpan);
DateTime? previous = null;
int quotient = 0;
var sortedDates = _dateTimes.OrderBy(x => x);
foreach (var dateTime in sortedDates)
{
if (previous != null)
{
total += dateTime - previous.Value;
}
++quotient;
previous = dateTime;
}
return quotient > 0 ? (TimeSpan.FromMilliseconds(total.TotalMilliseconds/quotient)) as TimeSpan? : null;
}
}
}