Round DateTime using SQL DATETIME rules - c#

Say there is some code that compares a DateTime object with a DateTime object that has been saved and returned from SQL Server, stored in a DATETIME object.
The comparison is done on hh:mm:ss-equality, but it is possible that SQL Server 'changes' the second component when saved which makes the comparisons fail ~1.5/1000 of the time or so.
This is because SQL Server will round/truncate this value when saved in the database as a DATETIME value:
datetime values are rounded to increments of .000, .003, or .007 seconds..
Is there a (standard) C#/.NET function that does the same rounding?
The primary goal of this question is to normalize the value prior to saving, for use in comparisons. That is, F(original) == F(saved) should always be true.
The final goal overall is to ensure the values are saved 'within the correct second', such that hh:mm:01.999 is stored as hh:mm:01.997. This would allow the hh:mm:ss-equality comparisons to be reliable regardless of if done to the original DateTime values or restored values. In this case, original.Second == F(original).Second should always be true as well.
For better or worse, one widely-used assumption is the comparison is done per hh:mm:ss, so a simple epsilon-compare of 2 milliseconds is out; although I wouldn't be opposed to a strongly-argued for comparison function that might also solve the final goal.

The SqlDateTime structure stores date/time values in the same way as SQL Server's datetime type. It provides conversions from and to the .NET DateTime type, and rounds when the conversion is performed.
Note that you have contradictory requirements in your question. You say you want the same rounding as SQL Server. You also say you want the rounding to never change the "second" component. You can't have it both ways. If you need the "second" component to not change, you may need to implement that yourself. You can check after the conversion has been done whether the second changed, and then restore it, or you may implement the conversion yourself to always round down.

Related

Millisecond is not removed from data

I have a DateTime column in my table. I want to store RUN date without millisecond stamp.
I tried below code, but in table, millisecond comes as 0000000.
Is that possible to store only date in "2021-06-09 08:58:03" format?
DateTime dt = Convert.ToDateTime(System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
Thanks in advance. Any help will be appreciated.
Upfront
Decide if you want to store a SQL datetime (or related) type or just a formatted string representation of a date and time.
Also decide if you want to use a local clock (of client workstation or even web server, with a clock that may be in a time zone different from your database server or maybe just out of time-sync with the database server).
Because
If the need is to be able to audit/report/coordinate the "order of events" you should choose an appropriate SQL type like datetime/datetime2/smalldatetime and instead of using client-side code to query the client-side clock, use SQL DATE and TIME functions MS T-SQL date and time types and functions. If you do this then record values at whatever precision they are offered and worry about string formatting when values are retrieved for display.
Only if you wish to store a string representation of a date and time in the database should you concern yourself with format at the time of insertion.
Final Answer
Consider a floating point numeric type like C#'s double. You can declare double d1 = 1.23E+2; or double d2 = 123. Both d1 and d2 contain an equal value (approximately 123.00...0 with about 13 zeroes all of which are significant). The format used at the time of assignment cannot influence future use of the value or system behaviour when it is used, whether for arithmetic or display purposes. The same applies to date and time types.
#mjwills comment taught me something I wasn't aware of: datetime2(0) seems to offer what you want - date and time with a precision of whole seconds, however fractions of a second are still recorded (try insert value CURRENT_TIMESTAMP to a datetime2(0) column), so treat the precision parameter as a storage-allocation hint rather than a hard limit on "significant digits recorded" and you will still need to worry about formatting values when retrieved for reporting/display.

Comparing DateTimes with Unspecified and UTC kinds

I have 2 DateTime values:
date1 <- {15-07-13 20:45:10} with Kind = Unspecified
date2 <- {15-07-13 20:45:10} with Kind = UTC
When comparing these 2 dates, the 2 dates are equal.
if (DateTime.Compare(date1, date2)!=0)
...
Can someone can explain why?
A little bit more strange to me: when converting the date1 (which is Unspecified kind) to UTC, I clearly see that the date is different:
date1.ToUniversalTime() --> {15-07-13 18:45:10} with Kind = UTC
Does someone can explain me why?
Yup. It's because DateTime is a fundamentally broken type, IMO. Basically the Kind isn't used in comparisons. Doing so would quite possibly have broken old code, and it's not always what you want. It was added on for .NET 1.1, and not always in a great way - it definitely wasn't fully integrated in every way you might have expected, as you've seen for comparisons.
As another example, even for a Kind of Local (which is meant to be the system local time zone) it's ignored for arithmetic, which means a call of AddHours(1) really only adds to the local time, rather than it representing elapsed time (which could end up being the same local time or two hours later in local time, around DST transitions).
My advice is just to avoid comparing DateTime values of different kinds in the first place. It's almost never what you want to do.
(Of course I'd also recommend using Noda Time, but that's a slightly different matter.)
From the documentation on DateTimeKind (emphasis is mine):
The members of the DateTimeKind enumeration are used in conversion
operations between local time and Coordinated Universal Time (UTC),
but not in comparison or arithmetic operations.

C#, WCF and DateTime.MinValue() vs. Null Date

I have searched around but have yet to find a satisfying answer for my issue:
Overview: I am working on a small app that uses several DateTime fields into a single table using WCF. We are trying to eliminate null fields in all of our tables. Up until now, whenever we are converting a datetime field selected from a table we first verify that it is null before we display or otherwise "use" it. If the value is NULL, we substitute DateTime.MinValue for the value. Since we are now removing the nullable aspect of all fields, we need to insert a common value representing null; since DateTime.MinValue() is substituted everywhere in the code, it seems like a viable value to put into the field as a null substitute.
The problem: Inserting a DateTime.MinValue() causes the generic "problem executing this request" error.
Solution? : As has been documented elsewhere, the DateTime.MinValue() has an unspecified DateTimeKind so... we add the ToUniversalTime call, as in :
DateTime nullDate = DateTime.MinValue.ToUniversalTime(); // 1/1/0001 6:00:00 AM
This doesn't work, perhaps because of some "acceptable range" setting within Date conversions on WCF?
Noted in the comment is the resulting value of "1/1/0001 6:00 am". The odd 6:00am or the year "1" aside, that seems fine. (Considering the past of default years like 12:00 AM 1970 are still fairly standard, but I digress...)
Yes, I know that DateTime is not C#, it's .NET. I'm also aware that WCF has some form of "min" and "max" times allowed within its conversion constraints. I don't know how to set those (nor do I know how to set the VerboseMessages bit; I'm fairly new to WCF.)
The suggestion to manually create a new field value that converts nicely into the table is viable, and it works:
DateTime nullDate = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
(BTW, If we use the year 1 instead of 1970 (for example) the insert will also fail.)
Even if we were to use the above nullDate as a workable substitute... the problem is that everywhere in the code we are still evaluating against DateTime.MinValue() representing a null date, and those two dates aren't quite the same.
The only viable solution that I can think of is to override or otherwise create an extended class from DateTime to create a common NullDate and modify all the code accordingly.
Does anyone see a good alternative? Does anyone know a solution to inserting the System.DateTime.MinValue() into a WCF table such as altering the acceptable boundries of a good/bad date?
I feel like I'm going to have to bite the bullet and change all references to MinValue... I'm trying to avoid that because it doesn't follow any sort of standard logical thought in evaluating "default" values.
Suggestions anyone?
It depends on the database you are using to store the data. SQL Server, for example, has a minimum date of 1/1/1753, see MSDN. I am not sure about Oracle.
If you Have to use a magic date, you can use that, or something else specific (I use 10 December 1815 in honor of the Lady Ada Lovelace, the first programmer). Generally speaking, if you have a situation where you have no known value, the data store should represent the value as 'null.' Now in practice, that's not always viable. What you could do is refactor the nullable columns into a subtable, and only include a child record when you in fact have something to record.

Why datetime cannot compare?

my C# unit test has the following statement:
Assert.AreEqual(logoutTime, log.First().Timestamp);
Why it is failed with following information:
Assert.AreEqual failed. Expected:<4/28/2010 2:30:37 PM>. Actual:<4/28/2010 2:30:37 PM>.
Are they not the same?
Update:
Use this if you only care to second:
Assert.AreEqual(logoutTime.ToString(), log.First().Timestamp.ToString());
Have you verified that the number of ticks/milliseconds are equal?
If you do DateTime.Now() twice back to back, they will appear to be the same number down to the minute and probably even down to the second, but they will often vary by ticks. If you want to check equality only to the minute, compare each DateTime only to that degree. For information on rounding DateTimes, see here
A note about resolution:
The Now property is frequently used to measure performance. However, because of its low resolution, it is not suitable for use as a benchmarking tool. A better alternative is to use the Stopwatch class.
Try something like Assert.AreEqual(logoutTime.Ticks, log.First().Timestamp.Ticks)
The Assert fail method is probably calling ToString() on the DateTime which returns a truncated, human-readable form of the date without the milliseconds component. This is why it appears they are equal when, in fact, the DateTime object has a precision of a 100-nanosecond unit (known as a Tick). That means it is highly unlikely two DateTime objects will have the exact same value. To compare you probably want to truncate the value, perhaps by formatting the date to the fidelity you require.
Using entity framework, if you fetch from the database using .AsNoTracking() the DateTime property will be rounded ever so slightly, whereas it won't necessarily be rounded without .AsNoTracking() if the original value is still in memory. Thus for integration tests involving a round-trip to the database, I guess it's best to use .ToString() because the database will reduce the precision slightly.
Are you sure that logoutTime and log.First().Timestamp are both typed as DateTime?
If so, they might also have different values for the more specific time infomation (e.g., milliseconds).
Assuming that logoutTime and log.First().Timestamp are both of type DateTime, you should try using this instead:
Assert.AreEqual(logoutTime.Ticks, log.First().Timestamp.Ticks);
While working on unit test, I found below steps very useful to compare some date with mock date.
Mock date field as below:
mockDate = new DateTime(2020, 10, 10)
Call service method.
Assertion can be done like this:
Assert.AreEqual("10/10/2020 12:00:00 AM", _service.startDate.ToString());
Note while doing assertion:
We have to provide date like : 10/10/2020 12:00:00 AM
Then on service method date item we need to apply ToString(), this will convert date time into string value for comparison
If we just have to do assertion with datetime today date
Assert.AreEqual(DateTime.Today, _service.startDate);
I suppose Assert.AreEqual<T> uses Object.Equals() to determine equality of the objects but not the values.
Probably this statement is comparing two different objects and therefore is returning false.

Handling and storing elapsed time

I'm having problems deciding on what is the best way is to handle and store time measurements.
I have an app that has a textbox that allows the users to input time in either hh:mm:ss or mm:ss format.
So I was planning on parsing this string, tokenizing it on the colons and creating TimeSpan (or using TimeSpan.Parse() and just adding a "00:" to the mm:ss case) for my business logic. Ok?
How do I store this as in a database though? What would the field type be? DateTime seems wrong. I don't want a time of 00:54:12 to be stored as 1901-01-01 00:54:12 that seems a bit poor?
TimeSpan has an Int64 Ticks property that you can store instead, and a constructor that takes a Ticks value.
I think the simplest is to just convert user input into a integer number of seconds. So 54:12 == 3252 seconds, so store the 3252 in your database or wherever. Then when you need to display it to the user, you can convert it back again.
For periods less than a day, just use seconds as other have said.
For longer periods, it depends on your db engine. If SQL Server, prior to version 2008 you want a datetime. It's okay- you can just ignore the default 1/1/1900 date they'll all have. If you are fortunate enough to have sql server 2008, then there are separate Date and Time datatypes you can use. The advantage with using a real datetime/time type is the use of the DateDiff function for comparing durations.
Most databases have some sort of time interval type. The answer depends on which database you're talking about. For Oracle, it's just a floating point NUMBER that represents the number of days (including fractional days). You can add/subtract that to/from any DATE type and you get the right answer.
As an integer count of seconds (or Milliseconds as appropriate)
Are you collecting both the start time and stop time? If so, you could use the "timestamp" data type, if your DBMS supports that. If not, just as a date/time type. Now, you've said you don't want the date part to be stored - but consider the case where the time period spans midnight - you start at 23:55:01 and end at 00:05:14, for example - unless you also have the date in there. There are standard build in functions to return the elapsed time (in seconds) between two date-time values.
Go with integers for seconds or minutes. Seconds is probably better. you'll never kick yourself for choosing something with too much precision. Also, for your UI, consider using multiple text inputs you don't have to worry about the user actually typing in the ":" properly. It's also much easier to add other constraints such as the minute and second values conting containing 0-59.
and int type should do it, storing it as seconds and parsing it back and forth
http://msdn.microsoft.com/en-us/library/ms187745.aspx

Categories