Related
I want to display minutes, seconds, and milliseconds saved in TimeSpan. It should look like this:
var d1 = new DateTime(2020, 12, 1, 12, 00, 00);
var d2 = new DateTime(2020, 12, 1, 10, 12, 30);
var d = d1 - d2;
Console.WriteLine(d.ToString(#"mm\:ss\:fff"));
But it returns 47:30:000 which is only partially true, because it ignored one hour. I want it to be converted into minutes, not ignored.
I think you need TimeSpan.TotalMinutes.
https://learn.microsoft.com/en-us/dotnet/api/system.timespan.totalminutes?view=net-7.0
In your case it could be:
Console.WriteLine($#"{(int)d.TotalMinutes}:{d:ss\:fff}");
TimeSpan has a property for getting the total number of minutes contained in it, as a double value: TimeSpan.TotalMinutes Property.
Note that if your TimeSpan's duration is not in whole minutes you will get a fraction part:
var d1 = new DateTime(2020, 12, 1, 12, 00, 00);
var d2 = new DateTime(2020, 12, 1, 10, 12, 30);
var d = d1 - d2;
Console.WriteLine(d.TotalMinutes);
Output:
107.5
Note that TimeSpan has similar properties for TotalDays, TotalHours, TotalSeconds, TotalMilliseconds.
Update:
Based on the comments below, here's a solution that prints in a format splitting minutes, seconds and milliseconds:
var d1 = new DateTime(2020, 12, 1, 12, 00, 00);
var d2 = new DateTime(2020, 12, 1, 10, 12, 30);
var d = d1 - d2;
double totalMin = d.TotalMinutes;
int totalMinInt = (int)totalMin; // will get only the whole minutes.
Console.WriteLine(totalMinInt.ToString() + // print the whole minutes part
":" +
d.ToString(#"ss\:fff")); // print the fraction part
Output:
107:30:000
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'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!
This question already has answers here:
Showing Difference between two datetime values in hours
(8 answers)
Closed 8 years ago.
I have a List of DateTimes
var times = CurrentMeter.SessionTimes.ToList();
How do I find the difference between them and add the results to a new List?
I want the hours, minutes, seconds and milliseconds between each datetime.
A difference between two DateTime objects is a TimeSpan.
This function will calculate the time delta between each consecutive pair of DateTimes.
It will iterate through each time in times, subtract the previous value from it, and add the difference to your result list.
IEnumerable<TimeSpan> CalculateDeltas(IEnumerable<DateTime> times) {
var time_deltas = new List<TimeSpan>();
DateTime prev = times.First();
foreach (var t in times.Skip(1)) {
time_deltas.Add(t - prev);
prev = t;
}
return time_deltas;
}
Here's an example to calculate the difference between each item in the list. The results are stored in a List<double> and represent the total of seconds between each datetime.
var times = new List<DateTime>
{
new DateTime(2014, 5, 28, 9, 57, 12),
new DateTime(2014, 5, 28, 9, 57, 43),
new DateTime(2014, 5, 28, 9, 58, 03),
new DateTime(2014, 5, 28, 9, 59, 46),
new DateTime(2014, 5, 28, 10, 0, 22),
};
var differences = new List<double>();
for(int i = 0; i < times.Count - 1; i++)
{
differences.Add((times[i+1] - times[i]).TotalSeconds);
}
Output:
31
20
103
36
How about:
var time = new []{
DateTime.Parse("14-4-2012 00:00:00"),
DateTime.Parse("14-4-2012 01:12:34"),
DateTime.Parse("14-4-2012 12:44:33"),
DateTime.Parse("14-4-2012 23:12:42"),
};
var result = time.Zip(time.Skip(1), (a, b) => b - a)
.Select(d => new {d.Hours, d.Minutes, d.Seconds})
.ToList();
result is now:
(Note that this is a very simply approach and only works for differences < 24h. If you have greater differences, take into account d.Days etc.)
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();