i have a list of "event" objects.
In every event i have "EventStartTime" and "EventEndTime" declared as DateTime objects.
I want to be able to search "events" by time , for example 10:00,
the "event" you see below shows that the festival starts at 22:00 on Feb 17th,
and ends at 15:00 the following day. i have a couple more like these.
new EventsManager.Event() //3
{
EventType = EventsManager.EventType.Festival,
EventName = "Twistival",
EventPlace = placeList[4],
EventStartTime =new DateTime(2017,02,17,22,0,0),
EventEndTime = new DateTime(2017,02,18,15,0,0),
EventNumberOfParticipants = 8000
},
So when i search for event that occur, or still occurring at at 10:00
i should get this event.
any suggestions?
Assuming that you have a specific time of day that you want to determine if the event covers regardless of the date it covers it on then there are 4 cases you need to consider. First if the dates are more than 1 day apart they cover all times of day. If the start is before the time of day and the end is after the time of day it will cover the time. The last two cases require that the end date be on the next day from the start date, then either the start date is before the time of day, or the end date is after the time of day. Note that this also assumes that the start date is before the end date.
var events = new List<Tuple<DateTime, DateTime>>
{
// start and end after time of day but on different days
Tuple.Create(
new DateTime(2017, 02, 17, 22, 0, 0),
new DateTime(2017, 02, 18, 15, 0, 0)),
// start and end before time of day but on different days
Tuple.Create(
new DateTime(2017, 02, 17, 9, 0, 0),
new DateTime(2017, 02, 18, 7, 0, 0)),
// start before and end after same day
Tuple.Create(
new DateTime(2017, 02, 17, 9, 0, 0),
new DateTime(2017, 02, 17, 11, 0, 0)),
// covers more than 1 day
Tuple.Create(
new DateTime(2017, 02, 17, 22, 0, 0),
new DateTime(2017, 02, 18, 22, 0, 1)),
// start after and end before on different days
Tuple.Create(
new DateTime(2017, 02, 17, 22, 0, 0),
new DateTime(2017, 02, 18, 10, 0, 0)),
// start and end before on same day
Tuple.Create(
new DateTime(2017, 02, 17, 7, 0, 0),
new DateTime(2017, 02, 17, 8, 0, 0)),
// start and end after on same day
Tuple.Create(
new DateTime(2017, 02, 17, 11, 0, 0),
new DateTime(2017, 02, 17, 12, 0, 0)),
};
var timeOfDay = new TimeSpan(0, 10, 0 ,0);
foreach (var x in events)
{
if (x.Item2 - x.Item1 > TimeSpan.FromDays(1)
|| (x.Item1.TimeOfDay < timeOfDay && x.Item2.TimeOfDay > timeOfDay)
|| (x.Item1.Date < x.Item2.Date
&& (x.Item1.TimeOfDay < timeOfDay || x.Item2.TimeOfDay > timeOfDay)))
{
Console.WriteLine(x);
}
}
Will output
(2/17/2017 10:00:00 PM, 2/18/2017 3:00:00 PM)
(2/17/2017 9:00:00 AM, 2/18/2017 7:00:00 AM)
(2/17/2017 9:00:00 AM, 2/17/2017 11:00:00 AM)
(2/17/2017 10:00:00 PM, 2/18/2017 10:00:01 PM)
Let's say you have a
List<Event> Events;
of your Events. You can create a simple LINQ query to get all events running at a special time with a simple method like
private IEnumerable<Event> GetRunningEvents(DateTime time)
{
return Events.Where(E => E.EventStartTime <= time && E.EventEndTime >= time);
}
Dont forget to add
using System.Linq;
to your file.
EDIT: Without LINQ a possible approach is
private List<Event> GetRunningEvents(DateTime time)
{
List<Event> RunningEvents = new List<Event>();
foreach(Event E in Events)
{
if (E.EventStartTime <= time && E.EventEndTime >= time)
{
RunningEvents.Add(E);
}
}
return RunningEvents;
}
Try Linq Where:
var list = new List<Event>();
var searchTime = DateTime.Now;
var result = list.Where(e => e.EventStartTime <= searchTime && searchTime <= e.EventEndTime).ToList();
Related
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
I have a number of "from time" and "to time" in a date.
For example:
from time | - to Time - |---- Date ---- | -- diff date --
-- 10:00 -- | -- 12:00 -- | 2019-08-07 | 2 Hours
-- 10:00 -- | -- 12:00 -- | 2019-08-07 | 2 Hours
-- 11:00 -- | -- 12:00 -- | 2019-08-07 | 1 Hours
-- 11:00 -- | -- 14:00 -- | 2019-08-07 | 3 Hours
-- 14:00 -- | -- 18:00 -- | 2019-08-07 | 4 Hours
-- 15:00 -- | -- 17:00 -- | 2019-08-07 | 2 Hours
-- 18:00 -- | -- 19:00 -- | 2019-08-07 | 1 Hours
Sum of the times above is: 15 Hours
But its wrong. because Some times are repetitive. Correct answer is 9 Hours.
How I can calculate Correct answer in this question?
This is harder than you might think, at least for the general case.
Here's a modified version of a class that I use for calculating ranges of numbers, accounting for overlapping regions (the full class also handles excluded regions, which I have included but am not using for this answer):
public sealed class RangeCombiner
{
public void Include(long start, long end)
{
_boundaries.Add(new Boundary(start, isStart: true, isIncluded: true));
_boundaries.Add(new Boundary(end, isStart: false, isIncluded: true));
_sorted = false;
}
public void Exclude(long start, long end)
{
_boundaries.Add(new Boundary(start, isStart: true, isIncluded: false));
_boundaries.Add(new Boundary(end, isStart: false, isIncluded: false));
_sorted = false;
}
public void Clear()
{
_boundaries.Clear();
}
public long TotalIncludedRange()
{
sortIfNecessary();
return totalIncludedRange();
}
void sortIfNecessary()
{
if (_sorted)
return;
_boundaries.Sort();
_sorted = true;
}
long totalIncludedRange()
{
int included = 0;
int excluded = 0;
long start = 0;
long total = 0;
for (int i = 0; i < _boundaries.Count; ++i)
{
if (_boundaries[i].IsStart) // Starting a region...
{
if (_boundaries[i].IsIncluded) // Starting an included region...
{
if (++included == 1 && excluded == 0) // Starting a new included region,
start = _boundaries[i].Value; // so remember its start time.
}
else // Starting an excluded region...
{
if (++excluded == 1 && included > 0) // Ending an included region,
total += _boundaries[i].Value - start; // so add its range to the total.
}
}
else // Ending a region...
{
if (_boundaries[i].IsIncluded) // Ending an included region...
{
if (--included == 0 && excluded == 0) // Ending an included region,
total += _boundaries[i].Value - start; // so add its range to the total.
}
else // Ending an excluded region...
{
if (--excluded == 0 && included > 0) // Starting an included region,
start = _boundaries[i].Value; // so remember its start time.
}
}
}
return total;
}
readonly List<Boundary> _boundaries = new List<Boundary>();
bool _sorted;
struct Boundary : IComparable<Boundary>
{
public Boundary(long value, bool isStart, bool isIncluded)
{
Value = value;
IsStart = isStart;
IsIncluded = isIncluded;
}
public int CompareTo(Boundary other)
{
if (this.Value < other.Value)
return -1;
if (this.Value > other.Value)
return 1;
if (this.IsStart == other.IsStart)
return 0;
if (this.IsStart)
return -1;
return 1;
}
public readonly long Value;
public readonly bool IsStart;
public readonly bool IsIncluded;
}
}
And here's how you use that for your problem. Note how I convert DateTime values into tick counts for the regions:
The output of the following code is Total = 09:00:00:
class Program
{
static void Main()
{
var combiner = new RangeCombiner();
var from1 = new DateTime(2019, 08, 07, 10, 00, 00);
var to1 = new DateTime(2019, 08, 07, 12, 00, 00);
var from2 = new DateTime(2019, 08, 07, 10, 00, 00);
var to2 = new DateTime(2019, 08, 07, 12, 00, 00);
var from3 = new DateTime(2019, 08, 07, 11, 00, 00);
var to3 = new DateTime(2019, 08, 07, 12, 00, 00);
var from4 = new DateTime(2019, 08, 07, 11, 00, 00);
var to4 = new DateTime(2019, 08, 07, 14, 00, 00);
var from5 = new DateTime(2019, 08, 07, 14, 00, 00);
var to5 = new DateTime(2019, 08, 07, 18, 00, 00);
var from6 = new DateTime(2019, 08, 07, 15, 00, 00);
var to6 = new DateTime(2019, 08, 07, 17, 00, 00);
var from7 = new DateTime(2019, 08, 07, 18, 00, 00);
var to7 = new DateTime(2019, 08, 07, 19, 00, 00);
combiner.Include(from1.Ticks, to1.Ticks);
combiner.Include(from2.Ticks, to2.Ticks);
combiner.Include(from3.Ticks, to3.Ticks);
combiner.Include(from4.Ticks, to4.Ticks);
combiner.Include(from5.Ticks, to5.Ticks);
combiner.Include(from6.Ticks, to6.Ticks);
combiner.Include(from7.Ticks, to7.Ticks);
Console.WriteLine("Total = " + TimeSpan.FromTicks(combiner.TotalIncludedRange()));
}
}
COMPLEXITY:
Adding the data is an O(N) operation
Calculating the total non-overlapping non-excluded is an O(N.Log(N))
operation.
Therefore adding and calculating is also O(N.Log(N)) overall.
Algorithm with LINQ in mind (Note that I do not write C#):
Have a class with fields: totime, fromtime, date, computed field for difference.
implement equals and hash methods for this class
transform the collection from DB to Set using LINQ. - HashSet<T> foo = new HashSet<T>(from x in bar.Items select x);
Transform Set back to list.
do a Aggregate with the differences to a sum.
Without LINQ things would be easier IMO.
Here is a working code:
https://dotnetfiddle.net/myhStL
This will give you a list of total hours for each different day.
List<DateTime> dates = new List<DateTime>(){
new DateTime(2019,1,1,10,0,0),
new DateTime(2019,1,1,12,0,0),
new DateTime(2019,1,1,13,0,0),
new DateTime(2019,1,1,14,0,0),
new DateTime(2019,1,2,10,0,0),
new DateTime(2019,1,2,12,0,0),
new DateTime(2019,1,2,14,0,0),
new DateTime(2019,1,3,10,0,0),
new DateTime(2019,1,3,11,0,0),
new DateTime(2019,1,3,12,0,0)
};
var result = dates
.OrderBy(d => d.Date)
.ThenBy(d => d.TimeOfDay)
.GroupBy(d => d.Date)
.Select(bla => new
{
Date = bla.First().Date,
Hours = bla.Last() - bla.First()
}).ToList();
Result:
Date: 1/1/2019 12:00:00 AM Hours: 04:00:00
Date: 1/2/2019 12:00:00 AM Hours: 04:00:00
Date: 1/3/2019 12:00:00 AM Hours: 02:00:00
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
private void GetTimeBetween()
{
DateTime a = new DateTime(2010, 04, 24, 13, 10, 00);
DateTime b = new DateTime(2010, 04, 25, 13, 10, 00);
Console.WriteLine(b.Subtract(a).TotalMinutes);
double d = b.Subtract(a).TotalMinutes;
}
I'm getting TotalMinutes 1440
But how can i make now that it will create a List with all the dates and time between this two dates between a and b but in jumps of 10 minutes ?
For example in the List the first item will be:
24/4/2010 13:10:00
Then ext item will be
24/4/2010 13:20:00
And so on until b
25/4/2010 13:10:00
In this format in the List or in other formats but the idea to get all the dates+time between the two given dates.
Gee, sometimes its fun to overengineer things. There is an Aggregate function which turns a list into a scalar value - lets create one that goes the other way
public static class Extensions
{
public static IEnumerable<T> Explode<T>(this T value, Func<T,T> next, Func<T,bool> limit)
{
var n = value;
while(!limit(n))
{
yield return n;
n = next(n);
}
}
}
Usage:
DateTime a = new DateTime(2010, 04, 24, 13, 10, 00);
DateTime b = new DateTime(2010, 04, 25, 13, 10, 00);
var result = a.Explode(x => x.AddMinutes(10), x => x>b).ToList();
Live example: http://rextester.com/WCGZL87983
You could loop it:
var list = new List<DateTime>();
var start = new DateTime(2010, 04, 24, 13, 10, 00);
var end = new DateTime(2010, 04, 25, 13, 10, 00);
for (DateTime date = start; date <= end; date = date.AddMinutes(10))
list.Add(date);
Try this
var start = new DateTime(2010, 04, 24, 13, 10, 00);
var end = new DateTime(2010, 04, 25, 13, 10, 00);
for (DateTime date = start; date <= end; date = date.AddMinutes(10))
{
Console.WriteLine(date.ToString("dd/mm/yyyy HH:mm:ss"));
}
I'm trying to calculate a due date for a service level agreement, and at the same time, I also need to back calculate the service level agreement in the other direction.
I've been struggling with calculations for "working time" (i.e. the time that work is possible during a set of days), and decided to use a third party library called TimePeriodLibrary.NET for the task. I need to be able to do two things:
Given a start DateTime and a TimeSpan, you should receive a DateTime of when a service level agreement date is due (date due).
Given a start DateTime and an end DateTime, you should receive a TimeSpan of how long that service level agreement should take.
All source code (test project is on GitHub). I have a ServiceLevelManager class that does all the work. It take a list of WorkDays and HolidayPeriods, in order to work out which hours are available to be worked. The CalendarPeriodCollector class is giving unexpected results. The expectations that do work in determining the due date from a timespan, do not calculate correctly when I back calculate them.
Can anyone see whether I am doing something wrong, or whether the library has a bug?
namespace ServicePlanner
{
using System;
using System.Collections.Generic;
using Itenso.TimePeriod;
public class ServicePlannerManager
{
public ServicePlannerManager(IEnumerable<WorkDay> workDays, IEnumerable<HolidayPeriod> holidays)
{
this.WorkDays = workDays;
this.Holidays = holidays;
}
public IEnumerable<WorkDay> WorkDays { get; set; }
public IEnumerable<HolidayPeriod> Holidays { get; set; }
public TimeSpan GetRemainingWorkingTime(DateTime start, DateTime dueDate)
{
var filter = new CalendarPeriodCollectorFilter();
foreach (var dayOfWeek in this.WorkDays)
{
filter.CollectingDayHours.Add(new DayHourRange(dayOfWeek.DayOfWeek, new Time(dayOfWeek.StartTime), new Time(dayOfWeek.EndTime)));
}
foreach (var holiday in this.Holidays)
{
filter.ExcludePeriods.Add(new TimeBlock(holiday.StartTime, holiday.EndTime));
}
var range = new CalendarTimeRange(start, dueDate);
var collector = new CalendarPeriodCollector(filter, range);
collector.CollectHours();
var duration = collector.Periods.GetTotalDuration(new TimeZoneDurationProvider(TimeZoneInfo.FindSystemTimeZoneById("UTC")));
return duration;
//var rounded = Math.Round(duration.TotalMinutes, MidpointRounding.AwayFromZero);
//return TimeSpan.FromMinutes(rounded);
}
}
}
The Unit tests that are failing are extracted below:
[TestFixture]
public class ServicePlannerManagerTest
{
[Test, TestCaseSource("LocalSource")]
public void GetRemainingWorkingTimeWithHolidayShouldOnlyEnumerateWorkingTime(DateTime startTime, TimeSpan workingHours, DateTime expectedDueDate, string expectation)
{
// Arrange
var workDays = new List<WorkDay>
{
new WorkDay(DayOfWeek.Monday, new DateTime(1, 1, 1, 9, 0, 0), new DateTime(1, 1, 1, 17, 0, 0)),
new WorkDay(DayOfWeek.Tuesday, new DateTime(1, 1, 1, 9, 0, 0), new DateTime(1, 1, 1, 17, 0, 0)),
new WorkDay(DayOfWeek.Wednesday, new DateTime(1, 1, 1, 9, 0, 0), new DateTime(1, 1, 1, 17, 0, 0)),
new WorkDay(DayOfWeek.Thursday, new DateTime(1, 1, 1, 9, 0, 0), new DateTime(1, 1, 1, 17, 0, 0)),
new WorkDay(DayOfWeek.Friday, new DateTime(1, 1, 1, 9, 0, 0), new DateTime(1, 1, 1, 17, 0, 0)),
};
var holidayPeriods = new List<HolidayPeriod>
{
new HolidayPeriod(new DateTime(2015, 9, 15, 00, 0, 0), new DateTime(2015, 9, 16, 0, 0, 0))
};
var service = new ServicePlannerManager(workDays, holidayPeriods);
// Act
var result = service.GetRemainingWorkingTime(startTime, expectedDueDate);
// Assert -
Assert.AreEqual(workingHours.TotalHours, result.TotalHours, expectation);
}
protected IEnumerable LocalSource()
{
yield return
new TestCaseData(
new DateTime(2015, 9, 14, 9, 0, 0),
new TimeSpan(23, 0, 0),
new DateTime(2015, 9, 17, 16, 0, 0),
"5. Expected 23 hours of working time to end on the 17/09/2015 16:00. Monday to Thursday evening. Just short of 3 full working days by one hour. Tuesday is holiday.");
}
}
Output of this test is
5. Expected 23 hours of working time to end on the 17/09/2015 16:00. Monday to Thursday evening. Just short of 3 full working days by one hour. Tuesday is holiday.
Expected: 23.0d
But was: 15.999999999944444d
I want to know if I am using the collector incorrectly, or if the collector has a bug.
This looks like a great library for solving a familiar problem.
The best thing to do is to output the periods in the period collection to help you debug the problem.
I've rewritten your test to use the base types in the examples from their documentation:
[Test, TestCaseSource("LocalSource")]
public void SO_GetRemainingWorkingTimeWithHolidayShouldOnlyEnumerateWorkingTime(DateTime startTime,
TimeSpan workingHours, DateTime expectedDueDate, string expectation)
{
CalendarPeriodCollectorFilter filter = new CalendarPeriodCollectorFilter();
filter.Months.Add(YearMonth.September); // only Januaries
filter.WeekDays.Add(DayOfWeek.Monday); //
filter.WeekDays.Add(DayOfWeek.Tuesday); //
filter.WeekDays.Add(DayOfWeek.Wednesday); //
filter.WeekDays.Add(DayOfWeek.Thursday); //
filter.WeekDays.Add(DayOfWeek.Friday); //
filter.CollectingHours.Add(new HourRange(9, 17)); // working hours
CalendarTimeRange testPeriod = new CalendarTimeRange(startTime, expectedDueDate);//new DateTime(2015, 9, 14, 9, 0, 0), new DateTime(2015, 9, 17, 18, 0, 0));
Console.WriteLine("Calendar period collector of period: " + testPeriod);
filter.ExcludePeriods.Add(new TimeBlock(new DateTime(2015, 9, 15, 00, 0, 0), new DateTime(2015, 9, 16, 0, 0, 0)));
CalendarPeriodCollector collector = new CalendarPeriodCollector(filter, testPeriod);
collector.CollectHours();
foreach (ITimePeriod period in collector.Periods)
{
Console.WriteLine("Period: " + period); // THIS WILL HELP A LOT!
}
var result = collector.Periods.GetTotalDuration(new TimeZoneDurationProvider(TimeZoneInfo.FindSystemTimeZoneById("UTC")));
Console.WriteLine(result);
//
}
This results in:
Calendar period collector of period: 14/09/2015 09:00:00 - 17/09/2015 15:59:59 | 3.06:59
Period: 14/09/2015 09:00:00 - 14/09/2015 16:59:59 | 0.07:59
Period: 16/09/2015 09:00:00 - 16/09/2015 16:59:59 | 0.07:59
15:59:59.9999998
So what I've noticed is that the very last period is missing.
If you change the end time of your period from 4PM to 6PM (and therefore expect an extra hour = 24) it will just about pass. (you will also need to round the result)
So it looks like the periods need to be completely covered by the total duration, partial coverage is not counted. You may be able to change the options of the library, alternatively you may be able to add each hour of the working day as separate CollectingHours (hacky)
Hope that gets you closer to the answer you need!
My requirement is to calculate a date based on another date.
The general rules needed for calculation are:
If input date occurs beetwen April 1st and September 30th (any year), the calculated result should be equal to December 31th of the input year
If input date occur between October 1st and March 31th, then the calculated result should be equal to "next" June 30th.
As per those requirements I have this code:
DateTime tmpDate = new DateTime( 2000, inputDate.Month, inputDate.Day );
DateTime aprilDate = new DateTime( 2000, 4, 1 );
DateTime septemberDate = new DateTime( 2000, 9, 30 );
DateTime endofYearDate = new DateTime( 2000, 12, 31 );
DateTime resultDate = DateTime.MaxValue;
if ( tmpDate >= aprilDate && tmpDate <= septemberDate ) {
resultDate = new DateTime( inputDate.Year, 12, 31, 23, 59, 59 );
}
else {
if ( tmpDate > septemberDate && tmpDate <= endofYearDate ) {
resultDate = new DateTime( inputDate.Year, 12, 31, 23, 59, 59 );
}
else {
resultDate = new DateTime( inputDate.Year + 1, 12, 31, 23, 59, 59 );
}
}
However I think that this code it's a little bit messy. How can I write it in a better way?
That looks far two complex:
if ( inputDate.Month > 3 && inputDate.Month < 10 ) {
resultDate = new DateTime( inputDate.Year, 12, 31, 23, 59, 59 );
}
else if (inputDate.Month > 9 ) {
//June, per spec rather than OPs code
resultDate = new DateTime( inputDate.Year + 1, 6, 30, 23, 59, 59 );
} else {
//Ditto
resultDate = new DateTime( inputDate.Year, 6, 30, 23, 59, 59 );
}
However, the presence of the times on these values does concern me. If you're going to be using these values as the end points for particular periods, I'd recommend instead computing the following July or January 1st and then using < comparisons rather than <=. You're far less likely to make mistakes such as excluding events that happen within the last second of the period.
DateTime delayedInputDate = inputDate.AddMonths(3);
if (delayedInputDate.Month < 7)
resultDate = new DateTime(delayedInputDate.Year, 6, 30);
else
resultDate = new DateTime(delayedInputDate.Year, 12, 31);
I'm expecting the following sequence of time data in 30 minute interval from another system. I need to make this 30 minute pair in to one hour interval:
e.g.
Source Data:
Start:7:00
End: 7:30
Start:7:30
End: 8:00
Start:8:00
End: 8:30
Start:8:30
End: 9:00
Convert in to hour data like below:
Start:7:00
End: 8:00
Start:8:00
End: 9:00
If there's no match for 30 minute block this record should be ignored:
e.g
Start:7:00
End: 7:30
Start:7:30
End: 8:00
Start:8:00
End: 8:30
Start:8:30
End: 9:00
Start:9:00 <-- Ignore this
End: 9:30 <-- Ignore this
Can someone please suggest how this can be achieved?
The general idea for my solution is that you simply only take elements at indices:
0, 3, 4, 7, 8, 11, 12, ...
Or in other words - if index % 4 is 0 or 3.
Before doing that, we check if there are leftover dates at the end with count % 4.
If there are - we cut them.
This assumes that the dates will be all valid as the input you illustrated.
Here is my implementation:
public static IEnumerable<DateTime> CutTimes(IEnumerable<DateTime> source)
{
var count = source.Count();
var ignore = count % 4;
if (ignore != 0)
{
source = source.Take(count - ignore);
}
return source.Where((time, index) => index % 4 == 0 || index % 4 == 3);
}
Usage:
var times = new[]
{
new DateTime(2012, 5, 10, 7, 00, 0),
new DateTime(2012, 5, 10, 7, 30, 0),
new DateTime(2012, 5, 10, 7, 30, 0),
new DateTime(2012, 5, 10, 8, 00, 0),
new DateTime(2012, 5, 10, 8, 00, 0),
new DateTime(2012, 5, 10, 8, 30, 0),
new DateTime(2012, 5, 10, 8, 30, 0),
new DateTime(2012, 5, 10, 9, 00, 0),
new DateTime(2012, 5, 10, 9, 00, 0),
new DateTime(2012, 5, 10, 9, 30, 0)
};
var result = CutTimes(times);
foreach (var time in result)
{
Console.WriteLine(time);
}
Output:
10/05/2012 07:00:00
10/05/2012 08:00:00
10/05/2012 08:00:00
10/05/2012 09:00:00