How to Find Contiguous Dates within two Sets of Dates - c#

All, I have two sets of dates for which I need to find all date pairs having contiguous dates where the hours connect. I would like the result to return the start and end date for each contiguous pair or pairs of dates to be used later as arguments to a linq query against a master list which contains all possible dates.
Below : Sample data to be evaluated. Desired output at bottom.
Thanks
// The result should return all pairs of start/end times (hour) where dates are contiguous. Where a set has more than two pairs,
// the result would return the start time of the first start record and the end time of the latest end record.
var ranges = new List<DateRange>
{new DateRange(DateTime.Parse("1/19/2018 10:00 AM"), DateTime.Parse("1/19/2018 12:00 PM")),
new DateRange(DateTime.Parse("1/19/2018 12:00 PM"), DateTime.Parse("1/19/2018 02:00 PM")),
new DateRange(DateTime.Parse("1/19/2018 02:00 PM"), DateTime.Parse("1/19/2018 04:00 PM")),
new DateRange(DateTime.Parse("1/19/2018 07:00 PM"), DateTime.Parse("1/19/2018 08:00 PM")),
new DateRange(DateTime.Parse("1/19/2018 04:00 PM"), DateTime.Parse("1/19/2018 05:00 PM")),
new DateRange(DateTime.Parse("1/19/2018 10:00 PM"), DateTime.Parse("1/19/2018 11:00 PM"))
};
// Sample Result
// Set 1
// "1/19/2018 10:00 AM", "1/19/2018 05:00 PM"
// Set 2
// "1/19/2018 07:00 PM", "1/19/2018 08:00 PM"
// Set 3
// "1/19/2018 10:00 PM", "1/19/2018 11:00 PM"``

The example-code will not work. Because of this i have to guess what you want to do. Your example-data look like some log-in/out data of a single user.
My guess: You got no overlapping intervalls - every start is unique and there is no other start before it ends.
Example:
1/18/2018 10 AM - 1/18/2018 11 AM
1/18/2018 11 AM - 1/18/2018 01 PM
You want to merge this to..
1/18/2018 10 AM - 1/18/2018 01 PM
And this is not allowed, because it's overlapping:
1/18/2018 10 AM - 1/18/2018 01 PM
1/18/2018 11 AM - 1/18/2018 02 PM
If this is what you want to do, you should edit your question. But this could be your solution:
// A class for each intervall
public class MyIntervall
{
public MyIntervall(DateTime start, DateTime end)
{
this.Start = start;
this.End = end;
}
public DateTime Start { get; set; }
public DateTime End { get; set; }
}
public static void Main(params string[] args)
{
// Your data..
List<MyIntervall> intervalls = new List<MyIntervall>()
{
new MyIntervall(new DateTime(2018, 1, 19, 10, 0, 0), new DateTime(2018, 1, 19, 12, 0, 0)),
new MyIntervall(new DateTime(2018, 1, 19, 12, 0, 0), new DateTime(2018, 1, 19, 14, 0, 0)),
new MyIntervall(new DateTime(2018, 1, 19, 14, 0, 0), new DateTime(2018, 1, 19, 16, 0, 0)),
new MyIntervall(new DateTime(2018, 1, 19, 19, 0, 0), new DateTime(2018, 1, 19, 20, 0, 0)),
new MyIntervall(new DateTime(2018, 1, 19, 16, 0, 0), new DateTime(2018, 1, 19, 17, 0, 0)),
new MyIntervall(new DateTime(2018, 1, 19, 22, 0, 0), new DateTime(2018, 1, 19, 23, 0, 0))
};
MyIntervall lastIntervall = null;
List<MyIntervall> mergedIntervalls = new List<MyIntervall>();
// Loop through your list.
foreach (MyIntervall currenIntervall in intervalls.OrderBy(o => o.Start))
{
if (lastIntervall == null)
{
// Start-condition
lastIntervall = currenIntervall;
}
else if (lastIntervall.End == currenIntervall.Start)
{
// If the last intervall ends at the start of the next one, we merge them into one bigger intervall by moving the end.
lastIntervall.End = currenIntervall.End;
}
else
{
// If end doesn't match the next start, we know that the intervall is closed. Wo move to the next one.
mergedIntervalls.Add(lastIntervall);
lastIntervall = currenIntervall;
}
}
// In the end, we add the last intervall to the merge-list, because it has no following intervall.
mergedIntervalls.Add(lastIntervall);
// output our merge-result
int set = 0;
foreach (MyIntervall currenIntervall in mergedIntervalls)
{
Console.WriteLine("#{0}: Start {1}, End {2}", ++set, currenIntervall.Start, currenIntervall.End);
}
// this is the result of the merge
List<MyIntervall> resultList = new List<MyIntervall>()
{
new MyIntervall(new DateTime(2018, 1, 19, 10, 0, 0), new DateTime(2018, 1, 19, 17, 0, 0)),
new MyIntervall(new DateTime(2018, 1, 19, 19, 0, 0), new DateTime(2018, 1, 19, 20, 0, 0)),
new MyIntervall(new DateTime(2018, 1, 19, 22, 0, 0), new DateTime(2018, 1, 19, 23, 0, 0))
};
}

Related

Determine if two dates are within an allowed time span

I got a StartDateTime and EndDateTime that I have to validate.
For them to be valid they have to be within allowed hours which are fully customizable.
// allowed hours
allowedStart = new TimeSpan(08, 00, 0);
allowedEnd = new TimeSpan(20, 00, 0);
Now the two dates (StartDateTime and EndDateTime) are coming in (some example test cases)
// Valid date
obj1.StartDateTime = new DateTime(2020, 1, 30, 19, 10, 0);
obj1.EndDateTime = new DateTime(2020, 1, 30, 19, 20, 0);
// End date exceeding
obj2.StartDateTime = new DateTime(2020, 1, 30, 19, 50, 0);
obj2.EndDateTime = new DateTime(2020, 1, 30, 20, 15, 0);
// Start and end date exceeding
obj3.StartDateTime = new DateTime(2020, 1, 30, 20, 10, 0);
obj3.EndDateTime = new DateTime(2020, 1, 30, 20, 35, 0);
// Invalid (overnight) both exceeding
obj4.StartDateTime = new DateTime(2020, 1, 30, 23, 50, 0);
obj4.EndDateTime = new DateTime(2020, 1, 31, 0, 35, 0);
// Start to early
obj5.StartDateTime = new DateTime(2020, 1, 31, 7, 50, 0);
obj5.EndDateTime = new DateTime(2020, 1, 31, 8, 15, 0);
I was wondering if there isn't some already implemented function I haven't found since my brain is dying right now. I've been trying to implement this myself like this but the obj4 testcase still kills it:
if ((obj.StartDateTime.Date.Add(allowedStart) <= obj.StartDateTime) &&
(allowedEnd < allowedStart
? obj.EndDateTime <= obj.EndDateTime.Date.AddDays(1).Add(allowedEnd)
: obj.EndDateTime <= obj.EndDateTime.Date.Add(allowedEnd))))
{
// valid
}
else
{
// invalid
}
Let's start from single value
private static bool WithinSpan(DateTime value, TimeSpan from, TimeSpan to) =>
value >= value.Date.Add(from) && value <= value.Date.Add(to);
Now we can implement the same with two values:
private static bool WithinSpan(DateTime startDate, DateTime endDate,
TimeSpan from, TimeSpan to) =>
// startDate <= endDate && // you may want to add this condition as well
startDate >= startDate.Date.Add(from) && startDate <= startDate.Date.Add(to) &&
endDate >= startDate.Date.Add(from) && endDate <= startDate.Date.Add(to);
Demo:
TimeSpan allowedStart = new TimeSpan(08, 00, 0);
TimeSpan allowedEnd = new TimeSpan(20, 00, 0);
(DateTime, DateTime)[] tests = new (DateTime, DateTime)[] {
(new DateTime(2020, 1, 30, 19, 10, 0), new DateTime(2020, 1, 30, 19, 20, 0)),
(new DateTime(2020, 1, 30, 19, 50, 0), new DateTime(2020, 1, 30, 20, 15, 0)),
(new DateTime(2020, 1, 30, 20, 10, 0), new DateTime(2020, 1, 30, 20, 35, 0)),
(new DateTime(2020, 1, 30, 23, 50, 0), new DateTime(2020, 1, 31, 0, 35, 0)),
(new DateTime(2020, 1, 31, 7, 50, 0), new DateTime(2020, 1, 31, 8, 15, 0)),
};
Func<DateTime, DateTime, string> within =
(t1, t2) => $"{(WithinSpan(t1, t2, allowedStart, allowedEnd) ? "Yes" : "No")}";
string report = string.Join(Environment.NewLine, tests
.Select(test => $"{test.Item1:yyyy-MM-dd HH:mm:ss} .. {test.Item2:yyyy-MM-dd HH:mm:ss} : {within(test.Item1, test.Item2)}"));
Console.Write(report);
Outcome:
2020-01-30 19:10:00 .. 2020-01-30 19:20:00 : Yes
2020-01-30 19:50:00 .. 2020-01-30 20:15:00 : No
2020-01-30 20:10:00 .. 2020-01-30 20:35:00 : No
2020-01-30 23:50:00 .. 2020-01-31 00:35:00 : No
2020-01-31 07:50:00 .. 2020-01-31 08:15:00 : No
Edit:
Elaborated version is
private static bool WithinSpan(DateTime startDate, DateTime endDate,
TimeSpan from, TimeSpan to) {
// Empty Period
if (startDate > endDate)
return false;
// [from..to] within single day
if (to >= from)
return startDate >= startDate.Date.Add(from) && startDate <= startDate.Date.Add(to) &&
endDate >= startDate.Date.Add(from) && endDate <= startDate.Date.Add(to);
// [from..midnight..to]
if (startDate.Day == endDate.Day)
return startDate >= startDate.Date.Add(from) || endDate <= endDate.Date.Add(to);
else {
to = to.Add(TimeSpan.FromDays(1));
return startDate >= startDate.Date.Add(from) && startDate <= startDate.Date.Add(to) &&
endDate >= startDate.Date.Add(from) && endDate <= startDate.Date.Add(to);
}
}
which removes empty periods, and treats from > to TimeSpan as containing midnight.
Demo:
// from 22:00 to midnight and then up to 06:00
TimeSpan allowedStart = new TimeSpan(22, 00, 00);
TimeSpan allowedEnd = new TimeSpan(06, 00, 00);
(DateTime, DateTime)[] tests = new (DateTime, DateTime)[] {
(new DateTime(2020, 1, 30, 19, 10, 0), new DateTime(2020, 1, 30, 19, 20, 0)),
(new DateTime(2020, 1, 30, 19, 50, 0), new DateTime(2020, 1, 30, 20, 15, 0)),
(new DateTime(2020, 1, 30, 20, 10, 0), new DateTime(2020, 1, 30, 20, 35, 0)),
(new DateTime(2020, 1, 30, 23, 50, 0), new DateTime(2020, 1, 31, 0, 35, 0)),
(new DateTime(2020, 1, 30, 23, 00, 0), new DateTime(2020, 1, 30, 23, 35, 0)),
(new DateTime(2020, 1, 30, 3, 00, 0), new DateTime(2020, 1, 30, 4, 00, 0)),
(new DateTime(2020, 1, 31, 4, 50, 0), new DateTime(2020, 1, 31, 8, 15, 0)),
};
Func<DateTime, DateTime, string> within =
(t1, t2) => $"{(WithinSpan(t1, t2, allowedStart, allowedEnd) ? "Yes" : "No")}";
string report = string.Join(Environment.NewLine, tests
.Select(test => $"{test.Item1:yyyy-MM-dd HH:mm:ss} .. {test.Item2:yyyy-MM-dd HH:mm:ss} : {within(test.Item1, test.Item2)}"));
Console.Write(report);
Outcome:
2020-01-30 19:10:00 .. 2020-01-30 19:20:00 : No
2020-01-30 19:50:00 .. 2020-01-30 20:15:00 : No
2020-01-30 20:10:00 .. 2020-01-30 20:35:00 : No
2020-01-30 23:50:00 .. 2020-01-31 00:35:00 : Yes
2020-01-30 23:00:00 .. 2020-01-30 23:35:00 : Yes
2020-01-30 03:00:00 .. 2020-01-30 04:00:00 : Yes
2020-01-31 04:50:00 .. 2020-01-31 08:15:00 : No
Next Edit:
Shortened verison that magically works:
if (startDate > endDate) {
return false;
}
if (startDate.Day == endDate.Day && to < from) {
return startDate >= startDate.Date.Add(from) || endDate <= endDate.Date.Add(to);
}
if (to < from) {
to = to.Add(TimeSpan.FromDays(1));
}
return startDate >= startDate.Date.Add(from) && endDate <= startDate.Date.Add(to);
Following, is the exact piece of code I use, for this not exact goal, in my program:
public bool IsInsideTimeframe(DateTime firstStart, DateTime firstEnd, DateTime secondStart, DateTime secondEnd)
{
bool isInside;
if (firstStart.Ticks >= secondStart.Ticks && firstEnd.Ticks <= secondEnd.Ticks)
isInside = true;
else
isInside = false;
return isInside;
}
Let's say you have two time frames, one between 11:00-14:00, the second is 10:00-15:00. In order to check whether the first timespan is inside the second, you use the function in following way:
IsInsideTimeframe(11:00, 14:00, 10:00, 15:00)
You can simply run this function on both of the dates you wish to validate, with the "allowed time span" as the "second" date given.

Counting hours from a valid date

I have a small problem because I do not always understand how to use the lessons of the day, for example, I want the time from the list to have the day time recalculated from that date, but if I have a new time, that conversion counts from the new time. It works fine for me if I only have one time, but if I have two times, foreach the loop calculates me both times for the day.
This is my code:
public TimeSpan GetHoursForDay(DateTime day) {
TimeSpan time = TimeSpan.Zero;
foreach (var times in shouldWorkTime)
{
if (times.Valid_from > day) //here's the real problem for me, do i want the hours to count from that date, for example: for 1.1.2020 it doesn't need to take hours from 1.12.2019
continue;
if (day.DayOfWeek == DayOfWeek.Monday)
{
time += times.ShouldWorkMonday;
}
if (day.DayOfWeek == DayOfWeek.Tuesday)
{
time += times.ShouldWorkTuesday;
}
if (day.DayOfWeek == DayOfWeek.Wednesday)
{
time += times.ShouldWorkWednesday;
}
if (day.DayOfWeek == DayOfWeek.Thursday)
{
time += times.ShouldWorkThursday;
}
if (day.DayOfWeek == DayOfWeek.Friday)
{
time += times.ShouldWorkFriday;
}
if (day.DayOfWeek == DayOfWeek.Saturday)
{
time += times.ShouldWorkSaturday;
}
if (day.DayOfWeek == DayOfWeek.Sunday)
{
time += times.ShouldWorkSunday;
}
}
return time;
}
}
These are the values I get in the list:
var shouldWorkTime = new List<ShouldWorkTime>
{
new ShouldWorkTime
{
Valid_from = new DateTime(2019, 12, 01, 0, 0, 0),
ShouldWorkMonday = new TimeSpan(8,0,0),
ShouldWorkTuesday= new TimeSpan(7,0,0),
ShouldWorkWednesday= new TimeSpan(6,0,0),
ShouldWorkThursday= new TimeSpan(5,0,0),
ShouldWorkFriday= new TimeSpan(8,0,0),
ShouldWorkSaturday = new TimeSpan(0,0,0),
ShouldWorkSunday = new TimeSpan(0,0,0)
},
new ShouldWorkTime
{
Valid_from = new DateTime(2020, 01, 01, 0, 0, 0),
ShouldWorkMonday = new TimeSpan(4,0,0),
ShouldWorkTuesday= new TimeSpan(3,0,0),
ShouldWorkWednesday= new TimeSpan(6,0,0),
ShouldWorkThursday= new TimeSpan(5,0,0),
ShouldWorkFriday= new TimeSpan(9,0,0),
ShouldWorkSaturday = new TimeSpan(0,0,0),
ShouldWorkSunday = new TimeSpan(0,0,0)
}
};
for the day value, I always get for the current day from the calendar, so I want to be counted in this case for the days of 1.1.2020 values for the days that fall in the second count, and until then the values that fall in the first count.
so i need to return how many hours for a particular day a worker needs to make, but valid from the last date from (Valid_From).
How can I correct this? thank you all very much for your help
eg:
input 1.1.2020 output = 6,0,0;
input 1.12.2019 output = 0,0,0;
Here is a little modification of the Data structure.
Instead of a brunch of property The TimeSpan will be mapped to the DayOfWeek using a Dictionary. This will remove the need for a switch-case or a lot of If.
Using DayOfWeek as dictionary key ensure that only one TimeSpan is defined for a day.
public class WorkingTimeScheldure
{
public DateTime Start { get; set; }
public DateTime End { get; set; }
public Dictionary<DayOfWeek, TimeSpan> Scheldure { get; set; }
}
That way I can ask for the TimeSpan of a day using : Scheldure[test.DayOfWeek]
Note the addition of an End property. It may be usefull to select the right thing.
eg: 30/01/2020 is superior to 01/12/2019 and 01/01/2020..
So in order to take the most recent, I assume the list in ordered on Start date and pick the last one: .Last(x => x.Start <= day).
Online Demo
public class Program
{
static List<ShouldWork> WorkTimeScheldure;
public static void Main()
{
WorkTimeScheldure = new List<ShouldWork>
{
new ShouldWork
{
Start = new DateTime(2019, 12, 01, 0, 0, 0),
Scheldure= new Dictionary<DayOfWeek, TimeSpan>()
{
{(DayOfWeek)0, new TimeSpan(0,0,0)},
{(DayOfWeek)1, new TimeSpan(8,0,0)},
{(DayOfWeek)2, new TimeSpan(7,0,0)},
{(DayOfWeek)3, new TimeSpan(6,0,0)},
{(DayOfWeek)4, new TimeSpan(5,0,0)},
{(DayOfWeek)5, new TimeSpan(8,0,0)},
{(DayOfWeek)6, new TimeSpan(0,0,0)}
}
},
new ShouldWork
{
Start = new DateTime(2020, 01, 01, 0, 0, 0),
Scheldure = new Dictionary<DayOfWeek, TimeSpan>()
{
{(DayOfWeek)0, new TimeSpan(0,0,0)},
{(DayOfWeek)1, new TimeSpan(4,0,0)},
{(DayOfWeek)2, new TimeSpan(3,0,0)},
{(DayOfWeek)3, new TimeSpan(6,0,0)},
{(DayOfWeek)4, new TimeSpan(5,0,0)},
{(DayOfWeek)5, new TimeSpan(9,0,0)},
{(DayOfWeek)6, new TimeSpan(0,0,0)}
}
}
};
var testValues = new[] {
new DateTime(2019, 12, 01, 0, 0, 0),
new DateTime(2019, 12, 02, 0, 0, 0),
new DateTime(2019, 12, 03, 0, 0, 0),
new DateTime(2019, 12, 04, 0, 0, 0),
new DateTime(2019, 12, 05, 0, 0, 0),
new DateTime(2019, 12, 06, 0, 0, 0),
new DateTime(2019, 12, 07, 0, 0, 0),
new DateTime(2019, 12, 08, 0, 0, 0),
new DateTime(2020, 01, 01, 0, 0, 0),
new DateTime(2020, 01, 02, 0, 0, 0),
new DateTime(2020, 01, 03, 0, 0, 0),
new DateTime(2020, 01, 05, 0, 0, 0),
new DateTime(2020, 01, 05, 0, 0, 0),
new DateTime(2020, 01, 06, 0, 0, 0),
new DateTime(2020, 01, 07, 0, 0, 0),
new DateTime(2020, 01, 08, 0, 0, 0),
};
foreach (var test in testValues) {
// Perhaps there is many possible, so I took the Last.
var workingTime = WorkTimeScheldure.Last(x => x.Start <= day);
//Please handle the case where there is no matching scheludre for this date.
var houtToWork = workingTime.Scheldure[day.DayOfWeek].Hours;
Console.WriteLine(
$"{day.ToShortDateString()} , it's a {day.DayOfWeek}" +
$" I have to work {houtToWork} Hour{(houtToWork>1?"s":"")}!"
);
}
}
}
Result :
12/01/2019 , it's a Sunday I have to work 0 Hour!
12/02/2019 , it's a Monday I have to work 8 Hours!
12/03/2019 , it's a Tuesday I have to work 7 Hours!
12/04/2019 , it's a Wednesday I have to work 6 Hours!
12/05/2019 , it's a Thursday I have to work 5 Hours!
12/06/2019 , it's a Friday I have to work 8 Hours!
12/07/2019 , it's a Saturday I have to work 0 Hour!
12/08/2019 , it's a Sunday I have to work 0 Hour!
01/01/2020 , it's a Wednesday I have to work 6 Hours!
01/02/2020 , it's a Thursday I have to work 5 Hours!
01/03/2020 , it's a Friday I have to work 9 Hours!
01/04/2020 , it's a Saturday I have to work 0 Hour!
01/05/2020 , it's a Sunday I have to work 0 Hour!
01/06/2020 , it's a Monday I have to work 4 Hours!
01/07/2020 , it's a Tuesday I have to work 3 Hours!
01/08/2020 , it's a Wednesday I have to work 6 Hours!
The test times.Valid_from > day is false for all element in shouldWorkTime if the value of day is big enough. That why time can be incremented multiple time.
If you want to increment only once and on the first/last acceptable value of Valid_from, you should ensure that shouldWorkTime is sorted in increasing/decreasing order and ensure that the increment is done only once.
In fact you didn't need increment but just return the corresponding TimeSpan:
public TimeSpan GetHoursForDay(DateTime day) {
// shouldWorkTime should have been sorted once for all at creation.
// This code use the first acceptable Valid_from
// By using OrderByDescending we take the last (in date) entry
var math = shouldWorkTime
.Where(v => day >= v.Valid_from) // We take only valid entry
.OrderByDescending(v => v.Valid_from) // We sort only on valid entry
.FirstOrDefault(); // we take the last (in date) valid entry
if (match == null)
return TimeSpan.Zero;
switch (day.DayOfWeek)
{
case DayOfWeek.Monday:
return match.ShouldWorkMonday;
case DayOfWeek.Tuesday:
return match.ShouldWorkTuesday;
case DayOfWeek.Wednesday:
return match.ShouldWorkWednesday;
case DayOfWeek.Thursday:
return match.ShouldWorkThursday;
case DayOfWeek.Friday:
return match.ShouldWorkFriday;
case DayOfWeek.Saturday:
return match.ShouldWorkSaturday;
case DayOfWeek.Sunday:
return match.ShouldWorkSunday;
}
}
Edit:
To avoid code duplication, the ShouldWorkTime class may provide a GetWorkTimeForDayOfWeek:
public TimeSpan GetWorkTimeForDayOfWeek(DayOfWeek dayOfWeek) {
...
}
And instead of storing seven values in seven fields, you may take a look to collections. I will choose a Dictionary<DayOfWeek, TimeSpan>.

Round DateTime nearest nth minute

I'm trying to round a DateTime to the nearest 7 minute.
I've seen many rounding functions for c#, but for some reason, I'm getting different results to what I'm expecting.
Given the following time:
var d = new DateTime(2019, 04, 15, 9, 40, 1, 0);
If I want to round to the nearest 7th minute then I would expect the answer to be
2019-04-15 9:42:00 // 0, 7, 14, 21, 28, 35, 42 ?
Input / Expected result
new DateTime(2019, 04, 15, 9, 40, 0, 0); // 9:42
new DateTime(2019, 04, 15, 9, 03, 0, 0); // 9:07
new DateTime(2019, 04, 15, 9, 31, 0, 0); // 9:35
new DateTime(2019, 04, 15, 9, 21, 0, 0); // 9:21
new DateTime(2019, 04, 15, 9, 0, 0, 0); // 9:00
new DateTime(2019, 04, 15, 9, 58, 0, 0); // 10:00 (start again)
Various DateTime rounding functions that I've seen show the following answers, which I can't understand why unless I'm missing something
9:41 or
9:43
Example of rounding functions
public static DateTime RoundUp(this DateTime dt, TimeSpan d)
{
var modTicks = dt.Ticks % d.Ticks;
var delta = modTicks != 0 ? d.Ticks - modTicks : 0;
return new DateTime(dt.Ticks + delta, dt.Kind);
}
DateTime RoundUp(DateTime dt, TimeSpan d)
{
return new DateTime((dt.Ticks + d.Ticks - 1) / d.Ticks * d.Ticks, dt.Kind);
}
static DateTime RoundUpNMinute(DateTime dt, int n)
{
var minute = dt.Minute;
if (minute % n == 0)
return dt;
var minuteRoundUp = minute / n * n + n;
if(minuteRoundUp > 60)
return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, 0, 0, dt.Kind).AddHours(1);
else
return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, minuteRoundUp, 0, dt.Kind);
}
Got same results for all your examples.

How do I calculate timer intervals for monthly scheduling?

I'm building a Windows Service, and I want it to process a bunch of different background tasks at configurable intervals.
I'm having a hard time working out how to calculate the length of time that the timer needs to wait before kicking off some task.
I store a StartDate against the task, and an interval type: daily, weekly, monthly, etc. I've nailed daily, but can't work out how to do monthly...
The rules are:
If the StartDate is in the future, then wait the length of time between Now and the StartDate.
If the StartDate is in the past, then start the task at the same day-of-month/time as the StartDate but in the current/next month. So if the StartDate is March 15th at 09:00 and today is May 25th then the next time the task should run is June 15th at 09:00.
I have got so far with this. Here is a test app, which takes a number of test cases and tries to calculate the number of hours between the current time (as per the test case) and the fixed start time of the task:
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main() {
// Test cases
var times = new Dictionary<DateTime,TimeSpan> {
{ new DateTime(2016, 3, 1, 9, 0, 0), TimeSpan.FromHours(2016)},
{ new DateTime(2016, 3, 5, 3, 0, 0), TimeSpan.FromHours(1926)},
{ new DateTime(2016, 3, 5, 9, 0, 0), TimeSpan.FromHours(1920)},
{ new DateTime(2016, 3, 5, 15, 0, 0), TimeSpan.FromHours(1914)},
{ new DateTime(2016, 3, 24, 3, 0, 0), TimeSpan.FromHours(1470)},
{ new DateTime(2016, 3, 24, 9, 0, 0), TimeSpan.FromHours(1464)},
{ new DateTime(2016, 3, 24, 15, 0, 0), TimeSpan.FromHours(1458)},
{ new DateTime(2016, 4, 19, 9, 0, 0), TimeSpan.FromHours(840)},
{ new DateTime(2016, 4, 24, 3, 0, 0), TimeSpan.FromHours(726)},
{ new DateTime(2016, 4, 24, 9, 0, 0), TimeSpan.FromHours(720)},
{ new DateTime(2016, 4, 24, 15, 0, 0), TimeSpan.FromHours(714)},
{ new DateTime(2016, 4, 24, 21, 0, 0), TimeSpan.FromHours(708)},
{ new DateTime(2016, 5, 6, 3, 0, 0), TimeSpan.FromHours(438)},
{ new DateTime(2016, 5, 24, 3, 0, 0), TimeSpan.FromHours(6)},
{ new DateTime(2016, 5, 24, 9, 0, 0), TimeSpan.FromHours(0)},
{ new DateTime(2016, 5, 24, 15, 0, 0), TimeSpan.FromHours(738)},
{ new DateTime(2016, 5, 26, 3, 0, 0), TimeSpan.FromHours(702)},
{ new DateTime(2016, 5, 26, 9, 0, 0), TimeSpan.FromHours(696)},
{ new DateTime(2016, 5, 26, 15, 0, 0), TimeSpan.FromHours(690)},
{ new DateTime(2016, 6, 24, 3, 0, 0), TimeSpan.FromHours(6)},
{ new DateTime(2016, 6, 24, 9, 0, 0), TimeSpan.FromHours(0)},
{ new DateTime(2016, 6, 24, 15, 0, 0), TimeSpan.FromHours(714)},
{ new DateTime(2016, 7, 6, 3, 0, 0), TimeSpan.FromHours(438)},
{ new DateTime(2016, 7, 6, 9, 0, 0), TimeSpan.FromHours(432)},
{ new DateTime(2016, 7, 6, 15, 0, 0), TimeSpan.FromHours(426)},
{ new DateTime(2016, 7, 24, 3, 0, 0), TimeSpan.FromHours(6)},
{ new DateTime(2016, 7, 24, 9, 0, 0), TimeSpan.FromHours(0)},
{ new DateTime(2016, 7, 24, 15, 0, 0), TimeSpan.FromHours(738)},
};
var startTime = new DateTime(2016, 05, 24, 09, 00, 00);
var last = times.First().Key;
foreach (var time in times) {
var now = time.Key;
var expected = time.Value;
var timer = startTime.TimeOfDay - now.TimeOfDay;
if (now <= startTime)
timer += TimeSpan.FromDays((startTime.Date - now.Date).TotalDays);
else
timer += TimeSpan.FromDays((now.Date.AddMonths(1) - now.Date).TotalDays);
if (last.Date != now.Date) Console.WriteLine();
Console.WriteLine($"{now:yyyy-MM-dd HH:mm} -> {startTime:yyyy-MM-dd HH:mm} = {timer:dd\\.hh} {(timer != expected ? "EXPECTED " + expected.ToString("dd\\.hh") : "CORRECT ")}");
last = now;
}
}
}
Which produces the following output:
2016-03-01 09:00 -> 2016-05-24 09:00 = 84.00 CORRECT
2016-03-05 03:00 -> 2016-05-24 09:00 = 80.06 CORRECT
2016-03-05 09:00 -> 2016-05-24 09:00 = 80.00 CORRECT
2016-03-05 15:00 -> 2016-05-24 09:00 = 79.18 CORRECT
2016-03-24 03:00 -> 2016-05-24 09:00 = 61.06 CORRECT
2016-03-24 09:00 -> 2016-05-24 09:00 = 61.00 CORRECT
2016-03-24 15:00 -> 2016-05-24 09:00 = 60.18 CORRECT
2016-04-19 09:00 -> 2016-05-24 09:00 = 35.00 CORRECT
2016-04-24 03:00 -> 2016-05-24 09:00 = 30.06 CORRECT
2016-04-24 09:00 -> 2016-05-24 09:00 = 30.00 CORRECT
2016-04-24 15:00 -> 2016-05-24 09:00 = 29.18 CORRECT
2016-04-24 21:00 -> 2016-05-24 09:00 = 29.12 CORRECT
2016-05-06 03:00 -> 2016-05-24 09:00 = 18.06 CORRECT
2016-05-24 03:00 -> 2016-05-24 09:00 = 00.06 CORRECT
2016-05-24 09:00 -> 2016-05-24 09:00 = 00.00 CORRECT
2016-05-24 15:00 -> 2016-05-24 09:00 = 30.18 CORRECT
2016-05-26 03:00 -> 2016-05-24 09:00 = 31.06 EXPECTED 29.06
2016-05-26 09:00 -> 2016-05-24 09:00 = 31.00 EXPECTED 29.00
2016-05-26 15:00 -> 2016-05-24 09:00 = 30.18 EXPECTED 28.18
2016-06-24 03:00 -> 2016-05-24 09:00 = 30.06 EXPECTED 00.06
2016-06-24 09:00 -> 2016-05-24 09:00 = 30.00 EXPECTED 00.00
2016-06-24 15:00 -> 2016-05-24 09:00 = 29.18 CORRECT
2016-07-06 03:00 -> 2016-05-24 09:00 = 31.06 EXPECTED 18.06
2016-07-06 09:00 -> 2016-05-24 09:00 = 31.00 EXPECTED 18.00
2016-07-06 15:00 -> 2016-05-24 09:00 = 30.18 EXPECTED 17.18
2016-07-24 03:00 -> 2016-05-24 09:00 = 31.06 EXPECTED 00.06
2016-07-24 09:00 -> 2016-05-24 09:00 = 31.00 EXPECTED 00.00
2016-07-24 15:00 -> 2016-05-24 09:00 = 30.18 CORRECT
As you can see it goes a bit wrong towards the end. What are the calculations I need to work out the number of hours correctly?
Try this code:
foreach (var time in times)
{
var now = time.Key;
var expected = time.Value;
TimeSpan timer;
if (now <= startTime) {
// no need to do anything here - just substract
timer = startTime - now;
}
else {
// normalize start time to current month
var normalized = new DateTime(now.Year, now.Month, startTime.Day, startTime.Hour, startTime.Minute, startTime.Second);
if (normalized >= now) {
// normalized date is later in the same month - substract
timer = normalized - now;
}
else {
// normalized date is before current - move to next month
timer = normalized.AddMonths(1) - now;
}
}
if (last.Date != now.Date) Console.WriteLine();
Console.WriteLine($"{now:yyyy-MM-dd HH:mm} -> {startTime:yyyy-MM-dd HH:mm} = {timer:dd\\.hh} {(timer != expected ? "EXPECTED " + expected.ToString("dd\\.hh") : "CORRECT ")}");
last = now;
}

Get the closest time to 24:00:00 between less 23:00:00 to greater 24:00:00 in C#

I am trying to get the time closer to 24:00:00 between two values, before midnight and after midnight.
EDIT: This is just an an example of what I am trying to do. In this case I should get both items.
var dt1 = new DateTime(2014, 11, 11, 23, 50, 00);
var dt2 = new DateTime(2014, 12, 11, 00, 50, 00);
var l = new List<DateTime>();
for (int i = 0; i < l.Count - 1; i++)
{
TimeSpan ts1 = new TimeSpan(l[i].Hour, l[i].Minute, l[i].Second);
TimeSpan ts2 = new TimeSpan(l[i + 1].Hour, l[i + 1].Minute, l[i + 1].Second);
if (ts1.TotalHours <= 23 && ts2.TotalHours >= 00)
{
Console.WriteLine("00:00:00 - {0} {1} \n", ts1, ts2);
}
}
Thank you for any help and advise.
Your question is quite confusing and not totally clear what it is you're trying to achieve, but I've made some assumptions, and come up with what I think maybe what you're after:
var l = new List<DateTime> {
new DateTime(2014, 11, 11, 22, 0, 0),
new DateTime(2014, 11, 11, 23, 45, 0),
new DateTime(2014, 11, 11, 23, 55, 0),
new DateTime(2014, 11, 11, 23, 59, 59),
new DateTime(2014, 11, 12, 0, 0, 0),
new DateTime(2014, 11, 12, 0, 4, 0),
new DateTime(2014, 11, 12, 0, 15, 0),
new DateTime(2014, 11, 12, 1, 0, 0),
new DateTime(2014, 11, 12, 10, 0, 0),
};
for (int i = 0; i < l.Count - 1; i++) {
if (l[i].TimeOfDay.TotalMinutes < 5 || l[i].TimeOfDay.TotalMinutes >= 23*60 + 55)
Console.WriteLine("{0} is close to midnight", l[i]);
else
Console.WriteLine("{0} is NOT close to midnight", l[i]);
}
I've loaded the list of dates/times with some test data, and the code simply prints out whether each date/time is within 5 minutes either side of midnight.
Another attempt at answering you're ambiguous question is as follows:
var l = new List<DateTime> {
new DateTime(2014, 11, 11, 15, 0, 0), // 15:00:00
new DateTime(2014, 11, 11, 16, 0, 0), // 16:00:00
new DateTime(2014, 11, 11, 17, 0, 0), // 17:00:00
new DateTime(2014, 11, 11, 17, 20, 0), // 17:20:00
new DateTime(2014, 11, 11, 18, 15, 0), // 18:15:00
new DateTime(2014, 11, 11, 19, 0, 0), // 19:00:00
new DateTime(2014, 11, 11, 22, 0, 0), // 22:00:00
new DateTime(2014, 11, 11, 23, 45, 0), // 23:45:00
new DateTime(2014, 11, 11, 23, 50, 00), // 23:50:00
new DateTime(2014, 12, 11, 00, 50, 00), // 00:50:00
new DateTime(2014, 11, 12, 1, 0, 0), // 01:00:00
new DateTime(2014, 11, 12, 10, 0, 0), // 10:00:00
};
var time = new TimeSpan(18, 0, 0); // <- Set the target time here
var offsetBefore = new TimeSpan(1, 0, 0, 0).TotalMilliseconds - time.TotalMilliseconds;
var offsetAfter = time.TotalMilliseconds * -1;
var closestBefore =
l.Aggregate(
(current, next) =>
next.AddMilliseconds(offsetBefore).TimeOfDay.TotalMilliseconds > current.AddMilliseconds(offsetBefore).TimeOfDay.TotalMilliseconds
? next
: current);
var closestAfter =
l.Aggregate(
(current, next) =>
next.AddMilliseconds(offsetAfter).TimeOfDay.TotalMilliseconds < current.AddMilliseconds(offsetAfter).TimeOfDay.TotalMilliseconds
? next
: current);
Console.WriteLine("{0} is the closest date/time before {1}.", closestBefore, time);
Console.WriteLine("{0} is the closest date/time after {1}.", closestAfter, time);
Console.WriteLine("00:00:00 - {0} {1} \n", closestBefore, closestAfter);
// OUTPUTS:
// 11/11/2014 17:20:00 is the closest date/time before 18:00:00.
// 11/11/2014 18:15:00 is the closest date/time after 18:00:00.
// 00:00:00 - 11/11/2014 17:20:00 11/11/2014 18:15:00
This will return the closest date/time in the list to midnight that is before midnight, and also separately the closest date/time in the list to midnight that is after midnight.
Hope this helps!
Try this:
var dt1 = new DateTime(2014, 11, 11, 23, 50, 00);
var dt2 = new DateTime(2014, 12, 11, 00, 50, 00);
var dt1temp = new DateTime(dt1.Year, dt1.Month, dt1.Day, 00, 00, 00);
var dt2temp = new DateTime(dt2.Year, dt2.Month, dt2.Day, 00, 00, 00);
TimeSpan time1 = new TimeSpan();
TimeSpan time2 = new TimeSpan();
TimeSpan time24 = new TimeSpan(24, 0, 0);
time1 = dt1 - dt1temp;
time2 = dt2 - dt2temp;
if (time1.Hours >= 12) time1 = time24 - time1;
if (time2.Hours >= 12) time2 = time24 - time2;
string result = "";
if (time1 < time2) result = "Time1 nearer to 00:00";
else result = "Time2 nearer to 00:00";

Categories

Resources