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
How to subtract "year=117 month=1 day=28 hour=7 min=43 sec=10" from a DateTime in c#?
I have already tried like below
split the string using regex.
add each item with -ve sign to a current DateTime value.
But I think it's not an efficient way.
Can anyone help me?
You can use below Code as per you requirement to get desired Result. Replace your Date, Time, year values in "new System.DateTime(1996, 6, 3, 22, 15, 0);"
System.DateTime date1 = new System.DateTime(1996, 6, 3, 22, 15, 0);
System.DateTime date2 = new System.DateTime(1996, 12, 6, 13, 2, 0);
System.DateTime date3 = new System.DateTime(1996, 10, 12, 8, 42, 0);
// diff1 gets 185 days, 14 hours, and 47 minutes.
System.TimeSpan diff1 = date2.Subtract(date1);
// date4 gets 4/9/1996 5:55:00 PM.
System.DateTime date4 = date3.Subtract(diff1);
// diff2 gets 55 days 4 hours and 20 minutes.
System.TimeSpan diff2 = date2 - date3;
// date5 gets 4/9/1996 5:55:00 PM.
System.DateTime date5 = date1 - diff2;
you can try DateTime's subtraction.
for that first you have to make valid DateTime object from your information and then subtract that date from current date.
see below code,
int year = 117, month = 01, day = 28;
int hour = 07, minute = 43, second = 10;
DateTime timeToSubtract =
new DateTime(year > 0? year : 1, month > 0 ? month : 1, day > 0 ? day : 1, hour, minute, second);
DateTime subtractedDate =
new DateTime((DateTime.Now - timeToSubtract).Ticks);
as you can see, we are creating a date time object with information we have (date and time which should be subtracted form current date time) by, new DateTime(year, month, day, hour, minute, second) and then subtracting this from DateTime.Now, and then creating final date out of result of this subtraction.
here in last line we are creating a date (of past). this date is of specified time ago.
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();
I have some data in a list that looks like this :
Date , Value
10:00 , 10
10:15 , 25
10:30 , 68
10:45 , 78
11:00 , 33
11:15 , 44
11:30 , 52
11:45 , 19
12:00 , 20
12:15 , 61
Now I would like to group them like this :
10:15 , 25
10:30 , 68
10:45 , 78
11:00 , 33
11:15 , 44
11:30 , 52
11:45 , 19
12:00 , 20
Obviously, I cannot simply use group by Date.Hour. So I ended up with :
for (int z = 1; z < list.Count(); z = z + 4)
{
list.Skip(z).Take(4);
// more code
}
But sometimes, 1 quarter of hour is missing in my data. So all my grouped data is now wrong because every grouping is shifted by 15mins.
So, how should I do to group my data correctly ?
Thanks
Why can't you group by hour? You just have to include the date:
var hourGroups = list.Select(x => new
{
Element = x,
Day = x.Date.Date,
Hour = x.Date.AddMilliseconds(-1).Hour
})
.GroupBy(x => new{ x.Day, x.Hour });
I've subtracted one millisecond to get your strange grouping.
Tested with part of your sample data:
var list = new List<Element>() {
new Element{Date=new DateTime(2014, 11, 18, 10, 00, 00), Value=10},
new Element{Date=new DateTime(2014, 11, 18, 10, 15, 00), Value=25},
new Element{Date=new DateTime(2014, 11, 18, 10, 30, 00), Value=68},
new Element{Date=new DateTime(2014, 11, 18, 10, 45, 00), Value=78},
new Element{Date=new DateTime(2014, 11, 18, 11, 00, 00), Value=33},
new Element{Date=new DateTime(2014, 11, 18, 11, 15, 00), Value=44},
new Element{Date=new DateTime(2014, 11, 18, 11, 30, 00), Value=52},
};
foreach(var grp in hourGroups)
Console.WriteLine("Day:{0} Time:{1} Values:{2}",
grp.Key.Day, grp.Key.Hour, string.Join(",", grp.Select(x => x.Element.Value)));
Output:
Day:18.11.20140 Time:9 Values:10
Day:18.11.2014 Time:10 Values:25,68,78,33
Day:18.11.20140 Time:11 Values:44,52
As you can see, the first element with 10 o'clock belongs to the 9-hour group which seems to be desired.
If you want a List<Element> for each group:
List<List<Element>> hourGroups = list.Select(x => new
{
Element = x,
Day = x.Date.Date,
Hour = x.Date.AddMilliseconds(-1).Hour
})
.GroupBy(x => new{ x.Day, x.Hour })
.Select(g => g.Select(x => x.Element).ToList())
.ToList();
I would do this by using a TimeSpan variable, this is where you minus two date times from each other, i have an example below:
DateTime x = DateTime.now;
bool BeenHour = false;
while(!BeenHour)
{
DateTime y = DateTime.now
TimeSpan TimePassed = (x - y);
if(TimePassed.hours == 1)
{
//Do thing
}
}
datetimes.GroupBy(dt => dt.Minute == 0 ? dt.Hour-1 : dt.Hour)
Assuming your hours are multiple of quarters,
list.GroupBy(h=> h.AddMinutes(-15).Hour)....
Or generally list.GroupBy(h=> h.AddTicks(-1).Hour)....
I want to fetch values from database with specific intervals in C# and need a single query for that.
This is how my Database looks like
Id SensorId Value CreatedOn
------------------------------------------------
1 8 33.5 15-11-2012 5:48 PM
2 5 49.2 15-11-2012 5:48 PM
3 8 33.2 15-11-2012 5:49 PM
4 5 48.5 15-11-2012 5:49 PM
5 8 31.8 15-11-2012 5:50 PM
6 5 42.5 15-11-2012 5:50 PM
7 8 36.5 15-11-2012 5:51 PM
8 5 46.5 15-11-2012 5:51 PM
9 8 39.2 15-11-2012 5:52 PM
10 5 44.4 15-11-2012 5:52 PM
11 8 36.5 15-11-2012 5:53 PM
12 5 46.5 15-11-2012 5:53 PM
13 8 39.2 15-11-2012 5:54 PM
14 5 44.4 15-11-2012 5:54 PM
.. .. ..... ...................
The interval is in minutes.
So, if the interval is 10 minutes, then we need the values at 5:48, 5:58, 6:08 and so on...
I tried doing it with a while loop but it is taking a lot of time as i shoot multiple queries to the database.
Is there any way of getting the data in a single query?
You can use datepart along with a modulus to get the matching rows (eg, #interval = 10, #offset = 8):
SELECT * FROM table
WHERE datepart(minute, CreatedOn) % #interval = #offset
Edit
Note that the above isn't a general solution of selecting by intervals. It will work across hours (and therefore across days) for intervals like 2, 3, 4, 5 ... any minute interval which divides into 60.
If you want to use a strange interval like 7 minutes, then you'd have to define a starting time for the interval and calculate the total minutes for each row, inclusive of hours/days. At that point you'd be best to create an indexed, computed column on the table, based on a user-defined function that calculates the interval in question.
Here is how you can do it, explanation is contained within comments in code:
/*We want 10-minute intervals starting
from minimum date to next day same time*/
DECLARE #startDateTime DATETIME = (
SELECT MIN(CreatedOn)
FROM #yourTable
)
DECLARE #endDateTime DATETIME = DATEADD(DAY, 1, #startDateTime)
DECLARE #startDateTimeTable TABLE (dt DATETIME)
INSERT #startDateTimeTable VALUES (#startDateTime)
/*Create a table that contains relevant datetimes (10-minute
intervals from starting date to end date)*/
;WITH a AS (
SELECT dt
FROM #startDateTimeTable
UNION ALL
SELECT DATEADD(MINUTE, 10, a.dt)
FROM a
JOIN #startDateTimeTable b ON a.dt <= #endDateTime
)
SELECT *
INTO #requiredDateTimes
FROM a
OPTION (MAXRECURSION 32767)
/*Now join data table to datetime table to
filter out only records with datetimes that we want*/
SELECT *
FROM #yourTable a
JOIN #requiredDateTimes b ON
a.CreatedOn = b.dt
Here is an SQL Fiddle
Any of the answers that recommend using modulus (%) are making several assumptions:
You will always have a reading on every sensor at the exact minute in question
You will never have more than one reading in a minute per sensor.
You will never have to deal with intervals smaller than a minute.
These are probably false assumptions, so you need a different approach. First, make a map of all of the time points you are querying over. Then take the last reading from each sensor on or before that point.
Here's a full unit test showing how it can be done in pure linq-to-objects. You may need some minor changes to the query to get it to work in linq-to-sql, but this is the right approach. I used the exact sample data you provided.
As an aside - I hope you are recording your CreatedOn dates in UTC, or you will have ambiguity of sensor readings during daylight savings time "fall-back" transitions. You need to record as DateTime in UTC, or using DateTimeOffset. Either are an appropriate representation of instantaneous time. A DateTime with .Kind of Local or Unspecified is only a valid representation of calendar time, which is not appropriate for sensor readings.
[TestClass]
public class LinqIntervalQueryTest
{
public class Item
{
public int Id { get; set; }
public int SensorId { get; set; }
public double Value { get; set; }
public DateTime CreatedOn { get; set; }
}
[TestMethod]
public void Test()
{
var data = new[]
{
new Item { Id = 1, SensorId = 8, Value = 33.5, CreatedOn = new DateTime(2012, 11, 15, 17, 48, 0, DateTimeKind.Utc) },
new Item { Id = 2, SensorId = 5, Value = 49.2, CreatedOn = new DateTime(2012, 11, 15, 17, 48, 0, DateTimeKind.Utc) },
new Item { Id = 3, SensorId = 8, Value = 33.2, CreatedOn = new DateTime(2012, 11, 15, 17, 49, 0, DateTimeKind.Utc) },
new Item { Id = 4, SensorId = 5, Value = 48.5, CreatedOn = new DateTime(2012, 11, 15, 17, 49, 0, DateTimeKind.Utc) },
new Item { Id = 5, SensorId = 8, Value = 31.8, CreatedOn = new DateTime(2012, 11, 15, 17, 50, 0, DateTimeKind.Utc) },
new Item { Id = 6, SensorId = 5, Value = 42.5, CreatedOn = new DateTime(2012, 11, 15, 17, 50, 0, DateTimeKind.Utc) },
new Item { Id = 7, SensorId = 8, Value = 36.5, CreatedOn = new DateTime(2012, 11, 15, 17, 51, 0, DateTimeKind.Utc) },
new Item { Id = 8, SensorId = 5, Value = 46.5, CreatedOn = new DateTime(2012, 11, 15, 17, 51, 0, DateTimeKind.Utc) },
new Item { Id = 9, SensorId = 8, Value = 39.2, CreatedOn = new DateTime(2012, 11, 15, 17, 52, 0, DateTimeKind.Utc) },
new Item { Id = 10, SensorId = 5, Value = 44.4, CreatedOn = new DateTime(2012, 11, 15, 17, 52, 0, DateTimeKind.Utc) },
new Item { Id = 11, SensorId = 8, Value = 36.5, CreatedOn = new DateTime(2012, 11, 15, 17, 53, 0, DateTimeKind.Utc) },
new Item { Id = 12, SensorId = 5, Value = 46.5, CreatedOn = new DateTime(2012, 11, 15, 17, 53, 0, DateTimeKind.Utc) },
new Item { Id = 13, SensorId = 8, Value = 39.2, CreatedOn = new DateTime(2012, 11, 15, 17, 54, 0, DateTimeKind.Utc) },
new Item { Id = 14, SensorId = 5, Value = 44.4, CreatedOn = new DateTime(2012, 11, 15, 17, 54, 0, DateTimeKind.Utc) },
};
var interval = TimeSpan.FromMinutes(3);
var startDate = data.First().CreatedOn;
var endDate = data.Last().CreatedOn;
var numberOfPoints = (int)((endDate - startDate + interval).Ticks / interval.Ticks);
var points = Enumerable.Range(0, numberOfPoints).Select(x => startDate.AddTicks(interval.Ticks * x));
var query = from item in data
group item by item.SensorId
into g
from point in points
let itemToUse = g.LastOrDefault(x => x.CreatedOn <= point)
orderby itemToUse.CreatedOn, g.Key
select new
{
itemToUse.CreatedOn,
itemToUse.Value,
SensorId = g.Key
};
var results = query.ToList();
Assert.AreEqual(6, results.Count);
Assert.AreEqual(data[1].CreatedOn, results[0].CreatedOn);
Assert.AreEqual(data[1].Value, results[0].Value);
Assert.AreEqual(data[1].SensorId, results[0].SensorId);
Assert.AreEqual(data[0].CreatedOn, results[1].CreatedOn);
Assert.AreEqual(data[0].Value, results[1].Value);
Assert.AreEqual(data[0].SensorId, results[1].SensorId);
Assert.AreEqual(data[7].CreatedOn, results[2].CreatedOn);
Assert.AreEqual(data[7].Value, results[2].Value);
Assert.AreEqual(data[7].SensorId, results[2].SensorId);
Assert.AreEqual(data[6].CreatedOn, results[3].CreatedOn);
Assert.AreEqual(data[6].Value, results[3].Value);
Assert.AreEqual(data[6].SensorId, results[3].SensorId);
Assert.AreEqual(data[13].CreatedOn, results[4].CreatedOn);
Assert.AreEqual(data[13].Value, results[4].Value);
Assert.AreEqual(data[13].SensorId, results[4].SensorId);
Assert.AreEqual(data[12].CreatedOn, results[5].CreatedOn);
Assert.AreEqual(data[12].Value, results[5].Value);
Assert.AreEqual(data[12].SensorId, results[5].SensorId);
}
}
Here's how you can do it in two calls to the database (untested):
int interval = 10;
DateTime firstDate = db.Items.Select(x => x.CreatedOn).Min();
var items = db.Items.Where(x => (x.CreatedOn - firstDate).TotalMinutes % interval == 0).ToList();