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
Related
I have the following algorithm which works fine for smaller date ranges, however if I increase the date range to about a year (startDate, endDate) it obviously drops performance because I loop every minute of each day, is there a way to improve the performance by using different list types such as hashsets or dictionaries or are there other fallbacks I'm not aware of?
listWorkTime contains around 300+ entries where some date ranges could overlap or being the same but having different TimeRangeId
private List<DateSharedWork> CalculateDateSharedWork(DateTime startDate,
DateTime endDate, ICollection<WorkTime> listWorkTime)
{
List<DateSharedWork> listDateSharedWork = new List<DateSharedWork>();
// +1 to include last day at full
int range = endDate.Subtract(startDate).Days + 1;
// start at startDate
Parallel.For(0, range, i =>
{
DateTime currDate = startDate.AddDays(i);
//set minute interval
double everyNMinutes = 1.0;
double minutesADay = 1440.0;
// reset counter
int work_counter = 0;
int lowWork_counter = 0;
int noWork_counter = 0;
int l = (int)(minutesADay / everyNMinutes);
for (int j = 0; j < l; j++)
{
DateTime check15 = currDate.AddMinutes(j * everyNMinutes);
// check if listWorkTime includes current date
var foundTime = listWorkTime
.Where(x => check15 >= x.FromDate && check15 <= x.ToDate).ToList();
if (foundTime.Count(x => x.TimeRangeId == 1) > 0)
{
// found interval that is within work hours
work_counter++;
noWork_counter++;
}
else
{
if (foundTime.Count(x => x.TimeRangeId == 2) > 0)
{
// found intervall that is within low work hours
lowWork_counter++;
noWork_counter++;
}
}
};
double work = everyNMinutes / minutesADay * work_counter;
double lowWork = everyNMinutes / minutesADay * lowWork_counter;
double noWork = 1.0 - (everyNMinutes / minutesADay * noWork_counter);
listDateSharedWork.Add(new DateSharedWork(currDate, work, lowWork, noWork));
});
listDateSharedWork.Sort((x, y) => DateTime.Compare(x.Date, y.Date));
return listDateSharedWork;
}
Edit*
class definitions
public class DateSharedWork
{
public DateSharedWork(DateTime date, double? work = 0.0, double? lowWork = 0.0, double? noWork = 1.0)
{
this.Date = date;
this.Work = work.Value;
this.LowWork = lowWork.Value;
this.NoWork = noWork.Value;
}
public DateTime Date { get; private set; }
public double Work { get; private set; }
public double LowWork { get; private set; }
public double NoWork { get; private set; }
}
I would try to add a uint in DateSharedWork to store an integer as representation of the date
i.e. : 09-07-2021-10:05:12.13546 => 202109071005
You can add the calculation of this uint field in the DateSharedWork constructor. It will surely cost at the loading of data.
Or you can tried to add the field and the calculation in the underlying DB and performing the calculation when performing upsert.
In the end i think it might improve query performance in your code. At least I already saw this approach in a Data Cube perf hint.
You could speed up the calculation of the foundTime list, by limiting the search to only these WorkTime instances that are relevant with the currDate. To do this you must first build a Dictionary that has a DateTime as key, a List<WorkTime> as value:
Dictionary<DateTime, List<WorkTime>> perDay = new();
foreach (var workTime in listWorkTime)
{
for (var d = workTime.FromDate.Date; d < workTime.ToDate.Date.AddDays(1); d.AddDays(1))
{
if (!perDay.TryGetValue(d, out var list)) perDay.Add(d, list = new());
list.Add(workTime);
}
}
This constitutes an extra work that must be done before starting the calculations, but hopefully it will speed the calculations enough to compensate for the initial cost.
Then you will be able to replace this:
var foundTime = listWorkTime
.Where(x => check15 >= x.FromDate && check15 <= x.ToDate).ToList();
With this:
List<WorkTime> foundTime;
if (perDay.TryGetValue(currDate, out List<WorkTime> currDateList))
{
foundTime = currDateList
.Where(x => check15 >= x.FromDate && check15 <= x.ToDate)
.ToList();
}
else
{
foundTime = new();
}
I would also suggest to replace the Parallel.For loop with a normal for loop. The parallel programming has quite a lot of gotchas, ready to catch the unwary by surprise.
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);
The format is:
ElapsedTime(min, seconds, milliseconds) CurrentDate(day, month, year) DateTime(hours, minutes, seconds)
00:00:00 23/08/2020 12:24:37
00:00:00 23/08/2020 12:25:13
00:00:00 23/08/2020 12:25:18
I had a look at the Array.Sort methods, but not sure how to implement it. My idea is, sort them by ElapsedTime, less time goes first, and then display what date and what time they were recorded at. How can I sort this into an array?
FWIW, this is how I write to the text file, and also how I get and sort the time.
// Content of file
string content = timer.ts.ToString() + " " + System.DateTime.Now + "\n";
// Add text to file
File.AppendAllText(path, content);
timer is a reference to the Timer class, ts is stopwatch.Elapsed()
ts = stopwatch.Elapsed;
string elapsedTime = String.Format("{0:00}:{1:00}.{2:00}",
ts.Minutes, ts.Seconds, ts.Milliseconds / 10);
Note : Below is the concept that you can take reference from. This is not a running code
class Program
{
static void Main(string[] args)
{
Stopwatch st = Stopwatch.StartNew();
LogPerformance lf = new LogPerformance { TotalTicks = st.ElapsedTicks, LoggingText = "format of your lgging string" };
List<LogPerformance> performanceList = new List<LogPerformance>() {lf,.... };
LogPerformanceComparer comparer = new LogPerformanceComparer();
performanceList.Sort(comparer);
performanceList.ForEach(x => logger.Log(x.LoggingText));
}
public class LogPerformance
{
public long TotalTicks { get; set; }
public string LoggingText { get; set; }
}
public class LogPerformanceComparer : IComparer<LogPerformance>
{
public int Compare([NotNull] LogPerformance x, [NotNull] LogPerformance y)
{
return x.TotalTicks.CompareTo(y.TotalTicks);
}
}
}
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;
}
}
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;
}
}
}