Comparing equal datetimes returns false - c#

I have a query with how datetimes are compared/stored in C#. Consider the following code:
var createdDate = DateTime.Now;
using (cr = new LanguageDictionaryRepository(ds)) {
cr.Add(new Sybrin10.Data.DTO.LanguageDictionary() {
Active = true,
CreatedDate = createdDate,
CultureCode = cultureCode,
Data = new System.Text.UTF8Encoding().GetBytes("Test")
});
cr.Save();
var y = cr.FindBy(x => x.CultureCode == cultureCode && x.CreatedDate == createdDate).FirstOrDefault();
Assert.IsNotNull(y);
The Assert.IsNotNull is failing because of the datetime check. I would expect that as the LanguageDictionary instance is created with the variable's value that the two would be equal. This is using Telerik.OpenAccess and MSSQL as a DB layer so I'm assuming the problem comes in there. Can anyone tell me if there is anything I'm missing with this and how to correctly compare these values.
EDIT: The tick values are different but I don't know why as they both come from the same variable which I only assign to once.

Try using DateTime.Equals(x.CreatedDate, createdDate), it might help.
Other than that, proper DateTime comparing is a massively complicated subject with timezones, offsets, utc, local time and whatnot. I wouldn't at all be suprised at a simple == compare between two seemingly identical dates to return false.
If the Ticks value differs on write and read, you're might be facing a DateTimeKind problem, where you're writing a DateTimeKind.Local to the database, but getting back an DateTimeKind.Unspecified.
The other option could be (if the difference is small enough) that the DateTime field in your database is not significant enough to store the same amount of milliseconds as the .net DateTime:
A single tick represents one hundred nanoseconds or one ten-millionth of a second. There are 10,000 ticks in a millisecond.
Depending on your data storage, it might not be as detailed as this. Your DateTime values do not come from the same source, one is read from memory, the other is read from database.

SqlServer stores a datetime in (about) 3-millisecond increments.
datetime values are rounded to increments of .000, .003, or .007 seconds
A roundtrip of a DateTime through the database could thus be off by a few ms.
So you should not test for "exactly equal", but for "close enough"
var y = cr.FindBy(x => x.CultureCode == cultureCode &&
x.CreatedDate >= createdDate.AddMilliseconds(-5) &&
x.CreatedDate <= createdDate.AddMilliseconds(5))
.FirstOrDefault();
Late edit: an extension method
public static class Extensions
{
public static bool IsAboutEqualTo(this DateTime target, DateTime other)
=> Math.Abs((target - other).TotalMilliseconds) <= 4;
}
Usage
var y = cr.FindBy(x => x.CultureCode == cultureCode &&
x.CreatedDate.IsAboutEqualTo(createdDate))
.FirstOrDefault();
Do note that Entity Framework will not be able to translate this into SQL, it will only work in linq-to-objects.

I think it might be better for you to use DateTime.UtcNow when you store the data and then you don't have to worry about daylight saving time issues etc. You can then display it how you want later using the culture you pick.

Related

Comparing timespan object if it's between hours in C#

I want to filter an array of objects. The goal is to filter the objects between two timespan objects:
TimeSpan tsFrom = TimeSpan.Parse("16:00");
TimeSpan tsTo = TimeSpan.Parse("00:59");
For example I have this object:
TimeSpan dateObject = TimeSpan.Parse("22:05:22");
The problem comes when I try to compare if the dateObject is between tsFrom and tsTo:
if (dateObject > tsFrom && dateObject < tsTo)
{
// do something ....
}
This won't work for cases like that. Do you have any ideas how I can make this work ?
You're wanting to works with times of day. The TimeSpan data type works with time spans (somewhat obvious to say). These are distinct concepts.
Times of day are precisely what motivated the creation of the new TimeOnly type in .NET 6. It has an IsBetween method:
Determines if a time falls within the range provided. Supports both "normal" ranges such as 10:00-12:00, and ranges that span midnight such as 23:00-01:00.
Note that IsBetween use the more sensible convention of inclusive start, exclusive end which means that you'd use 01:00 as the to moment and not accidentally exclude e.g. 00:59:17 from your period
For older versions, I'd suggest you realise that if To is less than From (e.g. it's representing a period that crosses midnight) you should check:
if ((tsTo > tsFrom && dateObject >= tsFrom && dateObject < tsTo) ||
(tsFrom > tsTo && (dateObject < tsTo || dateObject >= tsFrom)))
{
}
Note, again, that this logic is assuming inclusive From and exclusive To (Your original logic treated both ends as exclusive which is rarely correct)
The problem with this one is that you're trying to use the timeSpan method in the wrong way, if I got it right you are trying to check if the dateObject time comes between tsFrom and tsTo, the problem is that you are trying to refer to a different day with the tsTo, something that timespan can not handle.
I fixed it using dateTime to specify at least the day the time is taken from, i suggest changing the code to this
string datevalue = #"15/03/2021 16:00:00";
DateTime start = DateTime.Parse(datevalue);
datevalue = #"16/03/2021 00:59:00";
DateTime end = DateTime.Parse(datevalue);
datevalue = #"15/03/2021 22:05:22";
DateTime obj = DateTime.Parse(datevalue);
if (obj > start && obj < end)
{
//Do something
}

Find the closest previous DateTime from a list of DateTime

Is there a faster way of obtaining the closest previous (past) DateTime from a list of DateTimes when compared to a specific time? (the list comes from a SQL database)
public DateTime GetClosestPreviousDateTime(List<DateTime> dateTimes, DateTime specificTime)
{
DateTime ret = null;
var lowestDifference = TimeSpan.MaxValue;
foreach (var date in dateTimes)
{
if (date >= specificTime)
continue;
var difference = specificTime- date;
if (difference < lowestDifference)
{
lowestDifference = difference;
ret = date;
}
}
return ret;
}
The source list will be sorted since the dates in the list come from a SQL database where they are written consecutively.
It depends what you mean by "faster". The algorithm you show is O(N) so no you won't get faster than that - if by faster you mean is there a way to not have to iterate over all dates.
But if you mean can you shave off a few microseconds with some code that doesn't emit quite as many op codes, then yes of course. But is that really the issue here?
The answer will also change based on the size of the list, how accurate you need the answer to be, whether we can make any assumptions on the data (e.g. is it already sorted).
dateTimes.Sort();
var closest = dateTimes[dateTimes.IndexOf(search) - 1];
Your problem is a classic search algorithm and binary search might suit you.
Sort list: dateTimes.Sort();
Apply Binary Search algo with similar logic as in your for statement.
dateTimes.Where(x => x < specificTime).Max()
or if you want to handle the case where none exist:
dateTimes.Where(x => x < specificTime).DefaultIfEmpty().Max()
Later edit: Now you introduce new information that the List<> is already sorted. That was not in the question before.
With a sorted List<>, your algorithm is silly since it foreaches on and on, even after having reached the point where the entries "pass" the threshold specificTime. You can use instead BinarySearch (assuming List<> is sorted in ascending order and contains no duplicates):
static DateTime GetClosestPreviousDateTime(List<DateTime> dateTimes, DateTime specificTime)
{
var search = dateTimes.BinarySearch(specificTime);
var index = (search < 0 ? ~search : search) - 1;
if (index == -1)
throw new InvalidOperationException("Not found");
return dateTimes[index];
}
If you want to do it faster, just ask the database for the value, it will know how to find the answer fast; do not fetch the entire List<> to memory first. Use SQL or LINQ to the database.

Date from and to filter without time c# mvc

I have 2 inputs in an mvc application:
date_from and date_to (this are Date only on view, not DateTime)
when I call a service to get the result filtered by those values I call
Result result = client.GetResults(from = date_from, to = date_to);
the logic in the GetResults do a linq on a EF5 like this:
context.Results.Where(r=> r.date >= date_from && r.date <= date_to);
since the view only have the date part of the DateTime, if I pass
from : 2013-12-01
to : 2013-12-01
The only results i get are those on hour 0:0:0
What I want to do is call the service with the to as the end of the date.
NOTE: I don't want to change service logic because time is used in other places.
NOTE2: I don't want to send date_to.AddDays(1) since it will show me data from another date at 0:0:0 hour.
What's a good solution ? I came up with date_to.AddDays(1).AddMilliseconds(-1) but don't think is a good way to do it.
Thanks.
The simplest approach would be to add a day but change the upper bound to be exclusive:
var lowerBoundInclusive = date_from;
var upperBoundExclusive = date_to.AddDays(1);
context.Results.Where(r=> r.date >= lowerBoundInclusive &&
r.date < upperBoundExclusive);
Half-open intervals like this are nice, as they naturally abut - you can use the exclusive upper bound of one interval as the inclusive lower bound of the next one, etc - and every value will fall into exactly one interval. It also means that each boundary is nice round value, which is easy to read.
EDIT: Okay, with the comments it sounds like we're getting somewhere - the problem is that .NET uses DateTime when you're dealing with both "just dates" and "dates and times". Typically when expressing an interval with dates, you use an inclusive interval ("I'm on holiday Monday to Friday") whereas with dates and times you use an exclusive upper bound ("My first meeting is 3:00-4:00, my second is 4:00-5:00." - at 4:00 your first meeting has finished and the second one has started.)
I would recommend writing two methods, one of which can call the other:
// This is *inclusive* of both bounds
public XYZ GetResultsByDate(DateTime fromDate, DateTime toDate)
{
return GetResultsByDateAndTime(fromDate.Date, toDate.Date.AddDays(1));
}
// This is *exclusive* of the upper bound
public XYZ GetResultsByDateAndTime(DateTime from, DateTime to)
{
var results = context.Results.Where(r=> r.date >= from && r.date < to);
...
}

Data Structure for caching (Key = DateTime, Value = decimal)

I have a function which calculates employee hours in a day. But, it's slow because it considers carry hours and other things.
I figure I can optimize this by caching the hours in a day and only update on change.
I'm looking for something that can quickly do:
Set(date,hours)
HasHoursFor(date)
GetHoursFor(date)
What should I use for this?
I could use a Dictionary, but I am wondering if there is not some sort of hash way to set, get, and contains quickly.
You could use a Dictionary<DateTime,int> and cache the hours against the date, like so:
Dictionary<DateTime,int> dict = new Dictionary<DateTime,int>();
void Set(DateTime date, int hours)
{
if (dict.Contains(date)) dict.Remove(date);
dict.Add(date.Date,hours);
}
bool HasHoursForDate(DateTime date)
{
return dict.Contains(date.Date);
}
int GetHoursForDate(DateTime date)
{
return dict[date.Date];
}
Set(DateTime.Now,8);
I normalise the date so that's its JUST the date and doesn't care about the time part, otherwise that would throw off the comparison. I'm also assuming you have whole hours otherwise you might want to change int to double etc.

How can I average a DateTime field with a LINQ query?

I have a database that has the following records in a DateTime field:
2012-04-13 08:31:00.000
2012-04-12 07:53:00.000
2012-04-11 07:59:00.000
2012-04-10 08:16:00.000
2012-04-09 15:11:00.000
2012-04-08 08:28:00.000
2012-04-06 08:26:00.000
I want to run a linq to sql query to get the average time from the records above. I tried the following:
(From o In MYDATA Select o.SleepTo).Average()
Since "SleepTo" is a datetime field I get an error on Average(). If I was trying to get the average of say an integer, the above linq query works.
What do I need to do to get it to work for datetimes?
Internally, every DateTime is really stored as a number of ticks. The Ticks property of a DateTime is defined as the "number of 100-nanosecond intervals that have elapsed since 12:00:00 midnight, January 1, 0001." (See http://msdn.microsoft.com/en-us/library/system.datetime.ticks.aspx)
You can convert the DateTimes to ticks, then average, then convert back to a datetime.
var averageTicks = (long) dates.Select(d => d.Ticks).Average();
var averageDate = new DateTime(averageTicks);
Using your data structures and formatting, it would look like this:
var averageTicks = (long)(from o in MYDATA select o.SleepTo.Ticks).Average();
var averageDate = new DateTime(averageTicks);
If you want to get the average time of each SleepTo value (ignoring the Date component), you can get the ticks of just the time:
var averageTicks = (long)(from o in MYDATA select o.SleepTo.TimeOfDay.Ticks).Average();
var averageTime = new TimeSpan(averageTicks);
Here are a couple extensions methods that can help with this... There is a core problem where if you have a lot of DateTimes in a list the LINQ average of a the ticks (long vars) will overflow.
public static long Average(this IEnumerable<long> longs)
{
long count = longs.Count();
long mean = 0;
foreach (var val in longs)
{
mean += val / count;
}
return mean;
}
public static DateTime Average(this IEnumerable<DateTime> dates)
{
return new DateTime(dates.Select(x => x.Ticks).Average());
}
The database LINQ provider doesn't seem to understand how to do averages on absolute dates. Which, if you think about it, makes sense (average is a sum divided by a count - what is the sum?).
So, if you're not able to run the following:
(From o In MYDATA Select o.SleepTo).Sum()
Then you won't be able to do .Average() also.
Since what you want is actually the average time of SleepTo, you need to get just the time component of the date as a TimeSpan (time minus midnight perhaps) and average that. Do you by any chance have SleepFrom?
In the meantime, you might find this post enlightening: LINQ Average TimeSpan?

Categories