Group data by hour with linq and add next date - c#

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)....

Related

What is the best way to find is there any gap between number of ranges?

I am keeping staff clock-in times daily.
Sometimes they work the next day without taking any break.
WHAT I NEED IS
Without a break,the Total Hrs of staff who finish at midnight.
For example
Staff
Start
End
Staff A
9 AM
3 PM
Staff A
3 PM
10 PM
Staff A
10 PM
12:00 AM
Staff B
3 AM
9 PM
Staff B
10 PM
12:00 AM
Staff C
3 AM
9 PM
Staff D
10 PM
12:00 AM
In the above table, both staffs finished at midnight.
Staff A worked 15hrs without any breaks.
But Staff B had a break between 9 PM and 10 Pm.
Expected result:
Staff
Total
Staff A
15
Staff B
2
Staff C
0 because he didn't finish at midnight.
Staff C
2
I tried for loop, but it failed because each staff has a different number of entries, codes got messy at the end.
I tried LINQ and joining the same table etc. but couldn't figure it out.
This website is my last option.
Thanks advance for help.
Okay, this answer will need a bunch more work if you need this for work segments that cross dates or multiple dates. This assumes all work segments are in the same day.
The following groups all segments by staff, then adds up each segment total minutes. Then only returns the total if the last segment ends at midnight.
This test code returns the following:
Staff A: 15
Staff B: 20
Staff C: 0
Staff D: 2
Here's the code:
void Main()
{
var segments = GetTestData();
DateTime midnight = DateTime.Now.AddDays(1).Date;
var results = segments
.GroupBy(s => s.Name)
.Select(grp =>
new {
Name = grp.Key,
Total = grp.Max(g => g.EndDate) == midnight
? grp.Sum(g => g.EndDate.Subtract(g.StartDate).TotalMinutes)
: 0
});
foreach (var result in results)
{
Console.WriteLine($"{result.Name}: {result.Total/60F:#,##0}");
}
}
class Segment
{
public string Name { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
Segment[] GetTestData()
{
return new[]
{
new Segment { Name = "Staff A", StartDate = new DateTime(2023, 1, 18, 9 , 0, 0), EndDate = new DateTime(2023, 1, 18, 15, 0, 0) },
new Segment { Name = "Staff A", StartDate = new DateTime(2023, 1, 18, 15, 0, 0), EndDate = new DateTime(2023, 1, 18, 22, 0, 0) },
new Segment { Name = "Staff A", StartDate = new DateTime(2023, 1, 18, 22, 0, 0), EndDate = new DateTime(2023, 1, 19, 0 , 0, 0) },
new Segment { Name = "Staff B", StartDate = new DateTime(2023, 1, 18, 3 , 0, 0), EndDate = new DateTime(2023, 1, 18, 21, 0, 0) },
new Segment { Name = "Staff B", StartDate = new DateTime(2023, 1, 18, 22, 0, 0), EndDate = new DateTime(2023, 1, 19, 0 , 0, 0) },
new Segment { Name = "Staff C", StartDate = new DateTime(2023, 1, 18, 3 , 0, 0), EndDate = new DateTime(2023, 1, 18, 21, 0, 0) },
new Segment { Name = "Staff D", StartDate = new DateTime(2023, 1, 18, 22, 0, 0), EndDate = new DateTime(2023, 1, 19, 0 , 0, 0) }
};
}

C# - Searching Time between 2 DateTime Objects of different days

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();

DateTime algorithm suggestion

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);

How to find a dense region in a List<int>

hi i need to find the biggest dense region in a List of values based on a given range
example:
var radius =5; //Therm edited
var list = new List<int>{0,1,2,3,4,5,12,15,16,22,23,24,26,27,28,29};
//the following dense regions exist in the list above
var region1 = new List<int> { 0, 1, 2, 3, 4, 5 }; // exist 6 times (0, 1, 2, 3, 4, 5)
var region2 = new List<int> { 12, 15, 16}; // exist 3 times (12, 15, 16)
var region3 = new List<int> { 22, 23, 24, 26, 27}; // exist 1 times (22)
var region4 = new List<int> { 22, 23, 24, 26, 27, 28}; // exist 1 times (23)
var region5 = new List<int> { 22, 23, 24, 26, 27, 28, 29 }; // exist 3 times (24, 26, 27)
var region6 = new List<int> { 23, 24, 26, 27, 28, 29 }; // exist 1 times (28)
var region7 = new List<int> { 24, 26, 27, 28, 29 }; // exist 1 times (29)
//var result{22,23,24,26,27,28,29}
the solution doesn't really need to be fast because the max number of values is 21
is there an way to use fluent to achieve this?
i only know how to get the closest value
int closest = list.Aggregate((x,y) => Math.Abs(x-number) < Math.Abs(y-number) ? x : y);
and how to get values between 2 numbers
var between = list.Where(value=> min < value && value < max);
Edit
additional information's
Ok range is maybe the wrong therm radius would be a better word.
I define the dense region as the largest count of all values between currenvalue-range and currenvalue + range we get the dense region
A rather cryptic (but short) way would be:
int w = 5; // window size
var list = new List<int> { 0, 1, 2, 3, 4, 5, 12, 15, 16, 22,
23, 24, 26, 27, 28, 29 };
var result = list.Select(x => list.Where(y => y >= x - w && y <= x + w))
.Aggregate((a, b) => (a.Count() > b.Count()) ? a : b);
Console.WriteLine(string.Join(",", result.ToArray()));
Prints
22,23,24,26,27,28,29
This code consists of 3 steps:
For a given x the snippet list.Where(y => y >= x - w && y <= x + w) gives all elements from the list that are in the cluster around x.
list.Select(x => ...) computes that cluster for every element of the list.
.Aggregate((a, b) => (a.Count() > b.Count()) ? a : b) takes the cluster of maximum size.

Get values from database with specific time interval

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();

Categories