How does SqlDateTime do its precision reduction
Asked Answered
P

3

5

Consider the following program:

DateTime dateTime = new DateTime(634546165144647370);
SqlDateTime sqlDateTime = new SqlDateTime(dateTime);

Console.WriteLine("dateTime.TimeOfDay    = " + dateTime.TimeOfDay);
Console.WriteLine("sqlDateTime.TimeOfDay = " + sqlDateTime.Value.TimeOfDay);
Console.ReadLine();

That will have the following output:

dateTime.TimeOfDay    = 10:21:54.4647370  
sqlDateTime.TimeOfDay = 10:21:54.4630000

What is odd to me is that .464737 was rounded to .463. Shouldn't that have rounded to .464?

I assume I have not found a bug in .NET code, so the question is:

Why did it round to what it did? and how can I get client side rounding that will do what SqlServer is going to do?

As a side note, I saved this date time to a SQL Server database (in a DateTime column) and pulled it out again and it came out as 10:21:54.4670000. So I am really confused. (I thought SqlDateTime would match up with what SQL Server was going to do.)

Note: Because I am using OData I cannot use DateTime2 in SQL Server.

Picot answered 19/10, 2011 at 16:34 Comment(0)
F
8

SQL Server DATETIME has an accuracy of 3.33ms - therefore, you cannot get all possible values, and there's a good chance .464 was just such a value.

On SQL Server 2008, you could use DATETIME2 or TIME(x) datatypes which have an accuracy to 100ns - that should be plenty enough for "regular" use

Fabulist answered 19/10, 2011 at 16:37 Comment(6)
You might notice that OP said "NOTE: Because I am using OData I cannot use DateTime2 in SQL Server."Celestecelestia
@NicholasCarey: well in that case, we will have to live with the 3.33ms accuracy of DATETIME ...Fabulist
So is the 3.33 gap the same on each instance of SQL Server? Meaning are the missing values from "all possible values" the same from server to server? And should SqlDateTime have given me the same value for that date time tick count as my SQL Server did? (I did not, but should it have?)Picot
@Vaccano: yes, I believe the rounding effect should be the same on every SQL instance. Not sure what you mean about the SqlDateTime ....Fabulist
I am referring to the class System.Data.SqlTypes.SqlDateTime. It is billed as giving a .NET representation of a SQL Server DateTime value (with an underlying .net DateTime). However, when I fed it my .net DateTime (with the above number of ticks) it did not round/truncate the same as SQL Server did. (SQL Server DateTime = 467 while System.Data.SqlTypes.SqlDateTime = 463) I was wondering if those values should have been the same or if the .NET class does not really round the same as SQL Server.Picot
@Vaccano: The domains of the datetime and smalldatetime data types should be consistent across every instance and every version of SQL Server (at least, SQL Server 2000 or later). The CLR's SqlDateTime class does not spec that out. With Red Gate's excellent Reflector (reflector.net), you can crack the assembly open and inspect the source code of SqlDateTime to see what it does.Celestecelestia
C
6

SQL Server's datetype datatype is, internally, two 32-bit words (integers). The high-order word is the offset in days since the epoch (zero point) of the internal calendar used by SQL Server: that epoch is 1 January 1900 00:00:00.000. The low-order word is the offset in milliseconds since start-of-day (00:00:00.000/midnight).

For historic reasons, while the precision of the low-order word is 1 millisecond; the accuracy is 1/300th second (!?). That means that any given point-in-time is rounded to an increment of of 0, 3 or 7 milliseconds.

To do the conversion in the manner in which SQL Server does it, do the following: Take the milliseconds portion of the actual time under consideration, a value from 0-999, modulo 100. That gives you the low order digit, a value from 0-9. So if the current time is 23:57:23.559, the milliseconds component is 559. Modulo 100 you get 9.

  1. Values 0 and 1 are "rounded down" to 0.
  2. Values 2, 3 and 4 are "rounded down"" to 3.
  3. Values 5, 6, 7 and 8 are "rounded down" to 7.
  4. A value of 9 is rounded UP to 0. This has a rather unpleasant and nasty side effect: if the milliseconds portion of the time is 999, it ticks up 1 millisecond. This means that the time 23:59:59.999 is rounded up to THE NEXT DAY. So conversion of '31 Dec 2010 23:59:59.999' yields a datetime value of...1 January 2011 00:00:00.000.

Brilliant! Or something.

See the "Remarks" section under SQL Server 2005 BOL here: http://msdn.microsoft.com/en-us/library/ms187819(v=SQL.90).aspx

The upshot of all of this is that you can't do the obvious check for a date range/time period...something like

where myDateColumn between '1 September 2011 00:00:00.000'
  and '30 September 2011 23:59:59.999'

as that potentially brings in a [small] chunk of data from the next period. And you can't say

where myDateColumn between '1 September 2011 00:00'
  and '30 September 2011 23:59:59'

as that potentially excludes data that belongs in the period. Instead, you must say something like

  • where myDateColumn >= '1 September 2011 00:00:00.000' and myDateColumn < '1 October 2011 00:00:00.000', or

  • where myDateColumn >= '1 September 2011 00:00:00.000' and myDateColumn <= '30 September 2011 23:59:59.997'

It should be noted that smalldatetime, which has a precision of 1 minute, exhibits the same bogus behaviour: if the seconds component of the time under consideration is 29.998 seconds or less, it is rounded down to the nearest minute; if 29.999 or higher, it is rounded UP to the next minute, so the value 31 Dec 2010 23:59:30.000' winds up as asmalldatetimevalue of1 Jan 2011 00:00:00`.

This has all sorts of implications, especially WRT to billing systems and the like.

I would say that if precision is important to you, store your date/time values in SQL Server as strings in ISO 8601 format, something like 2011-10-30T23:59:55.1234 (or the equivalent 'compact' form (20111030T235955.1234). ISO 8601 collates and compares properly; it converts easily and it is human-readable. Even better split it out into two columns — one for date (2011-10-30) and one for time (23:59:55.1234). Then add a third, computed column to put it all together:

create table foo
(
  ...
  transaction_date char(10) not null ,
  transaction_time char(12) not null ,
  ...
  iso8601_transaction_datetime as transaction_date + 'T' + transaction_time ,
  ...
)

A pretty good summary of ISO 8601 is at http://www.cl.cam.ac.uk/~mgk25/iso-time.html. Wikipedia also has pretty good information: http://en.wikipedia.org/wiki/ISO_8601.

Celestecelestia answered 19/10, 2011 at 17:22 Comment(1)
His results are exactly what they are supposed to be: Depending one's definition of "correctness", those results may or may not be "correct". WRT to SQL Server 2008 date/time data types and OData, the OP says he can't use them: you might read his question. Never having futzed with OData, I've got no reason to doubt him. I suspect that the problem is more along the lines that OData support for SQL Server 2008 is limited.Celestecelestia
C
1

SqlDateTime Structure

Represents the date and time data ranging in value from January 1, 1753 to December 31, 9999 to an accuracy of 3.33 milliseconds to be stored in or retrieved from a database. The SqlDateTime structure has a different underlying data structure from its corresponding .NET Framework type, DateTime, which can represent any time between 12:00:00 AM 1/1/0001 and 11:59:59 PM 12/31/9999, to the accuracy of 100 nanoseconds. SqlDateTime actually stores the relative difference to 00:00:00 AM 1/1/1900. Therefore, a conversion from "00:00:00 AM 1/1/1900" to an integer will return 0.

I would argue that sqlDateTime is within that 3.33 ms accuracy.

The DateTime value type represents dates and times with values ranging from 12:00:00 midnight, January 1, 0001 Anno Domini (Common Era) through 11:59:59 P.M., December 31, 9999 A.D.

As a side note, I saved this date time to a SQL Server database (in a DateTime column) and pulled it out again and it came out as 10:21:54.4670000. So I am really confused. (I thought SqlDateTime would match up with what SQL Server was going to do.)

Time values are measured in 100-nanosecond units called ticks, and a particular date is the number of ticks since 12:00 midnight, January 1, 0001 A.D. (C.E.) in the GregorianCalendar calendar (excluding ticks that would be added by leap seconds). For example, a ticks value of 31241376000000000L represents the date, Friday, January 01, 0100 12:00:00 midnight. A DateTime value is always expressed in the context of an explicit or default calendar.

and

Internally, all DateTime values are represented as the number of ticks (the number of 100-nanosecond intervals) that have elapsed since 12:00:00 midnight, January 1, 0001. The actual DateTime value is independent of the way in which that value appears when displayed in a user interface element or when written to a file. The appearance of a DateTime value is the result of a formatting operation. Formatting is the process of converting a value to its string representation.

Because the appearance of date and time values is dependent on such factors as culture, international standards, application requirements, and personal preference, the DateTime structure offers a great deal of flexibility in formatting date and time values through the overloads of its ToString method. The default DateTime.ToString() method returns the string representation of a date and time value using the current culture's short date and long time pattern. The following example uses the default DateTime.ToString() method to display the date and time using the short date and long time pattern for the en-US culture, the current culture on the computer on which the example was run.

It looks like DateTime is accurate within 100ns which is about the difference you have experienced.

As a side note, I saved this date time to a SQL Server database (in a DateTime column) and pulled it out again and it came out as 10:21:54.4670000. So I am really confused. (I thought SqlDateTime would match up with what SQL Server was going to do.)

This is also within the 3.33 ms accuracy...If you need something more accurate then 3.33ms then you will have to use SQL Server 2008

Cartel answered 19/10, 2011 at 16:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.