I did some unit tests to answer my own question in all four parts.
1: Does SQL Server match DateTime.UtcNow or compensate it again depending on the time zone in which the server is installed, and then returns it with feedback when prompted?
This is done):
cmd.CommandText = "INSERT INTO testtbl (val) VALUES (@newval)"; cmd.Parameters.Add(new SqlParameter("@newval", DateTime.UtcNow)); cmd.ExecuteNonQuery(); cmd.CommandText = "SELECT CAST(val as varchar) value FROM testtbl"; Console.WriteLine(cmd.ExecuteScalar());
The result of this at 1:30 pm local time (-7h, or 8:30 UTC) was:
Jun 3 2010 8:30PM
Then I tried this:
cmd.CommandText = "INSERT INTO testtbl (val) VALUES (@newval)"; cmd.Parameters.Add(new SqlParameter("@newval", DateTime.UtcNow)); cmd.ExecuteNonQuery(); Console.WriteLine("change time zone to utc"); Console.ReadLine(); cmd.CommandText = "SELECT CAST(val as varchar) value FROM testtbl"; Console.WriteLine(cmd.ExecuteScalar()); Console.WriteLine("change time zone back to local");
Done at 21:25 UTC, it returned
Jun 3 2010 9:25PM
Compare this with DateTime.Now:
cmd.CommandText = "INSERT INTO testtbl (val) VALUES (@newval)"; cmd.Parameters.Add(new SqlParameter("@newval", DateTime.Now)); cmd.ExecuteNonQuery(); Console.WriteLine("change time zone to utc"); Console.ReadLine(); cmd.CommandText = "SELECT CAST(val as varchar) value FROM testtbl"; Console.WriteLine(cmd.ExecuteScalar()); Console.WriteLine("change time zone back to local");
Done at 15:55 (local; -7h), returned:
Jun 3 2010 3:55PM
2: So, I request it and drop it from the object on the DateTime after receiving it, say, from the IDataReader column. Does this casted System.DateTime object already know that it is a UTC DateTime instance, or did it assume that it was biased?
None.
cmd.CommandText = "INSERT INTO testtbl (val) VALUES (@newval)"; cmd.Parameters.Add(new SqlParameter("@newval", DateTime.UtcNow)); cmd.ExecuteNonQuery(); cmd.CommandText = "SELECT val value FROM testtbl"; var retval = (DateTime)cmd.ExecuteScalar(); Console.WriteLine("Kind: " + retval.Kind); Console.WriteLine("UTC: " + retval.ToUniversalTime().ToString()); Console.WriteLine("Local: " + retval.ToLocalTime().ToString());
The result of this (done at 13:58 local time):
Kind: Unspecified UTC: 6/4/2010 3:58:42 AM Local: 6/3/2010 1:58:42 PM
That is, .ToUniversalTime() finished the alignment from local time to UTC time not once, but twice (??), and .ToLocalTime() ultimately did not compensate at all.
3: Does ADO.NET pass the SQL Server offset and does SQL Server save DateTime.Now with offset metadata?
Without performing any unit tests, the answer is already known as an “only with DateTimeOffset” SQL type. SQL datetime does not perform offsets.
4: Does this cast System.DateTime object already know that this is the offset time, or does it assume it is UTC?
None. The SQL type DateTimeOffset is returned as a .NET DateTimeOffset structure.
The following runs at 15:31 local time, when the offval column is a type of SQL datetimeoffset,
cmd.CommandText = "INSERT INTO testtbl (offval) VALUES (@newval)"; cmd.Parameters.Add(new SqlParameter("@newval", DateTime.Now)); cmd.ExecuteNonQuery(); cmd.CommandText = "SELECT offval value FROM testtbl"; object retvalobj = cmd.ExecuteScalar(); Console.WriteLine("Type: " + retvalobj.GetType().Name); var retval = (DateTimeOffset)retvalobj; Console.WriteLine("ToString(): " + retval.ToString()); Console.WriteLine("UTC: " + retval.ToUniversalTime().ToString()); Console.WriteLine("Local: " + retval.ToLocalTime().ToString());
The result is:
Type: DateTimeOffset ToString(): 6/3/2010 3:31:47 PM +00:00 UTC: 6/3/2010 3:31:47 PM +00:00 Local: 6/3/2010 8:31:47 AM -07:00
Amazing mismatch.
Returning and performing the test for question No. 1 above, using DateTime.Now instead of DateTime.UtcNow, I confirmed that ADO.NET does NOT convert to universal time before storing in the database.
That is, this is done at 15:27 local time (-7h):
cmd.CommandText = "INSERT INTO testtbl (val) VALUES (@newval)"; cmd.Parameters.Add(new SqlParameter("@newval", DateTime.Now)); cmd.ExecuteNonQuery(); Console.WriteLine("change time zone to utc"); Console.ReadLine(); cmd.CommandText = "SELECT CAST(val as varchar) value FROM testtbl"; Console.WriteLine(cmd.ExecuteScalar()); Console.WriteLine("change time zone back to local");
.. is back ..
Jun 3 2010 3:27PM
Doing this at 3:17 pm local time:
cmd.CommandText = "INSERT INTO testtbl (val) VALUES (@newval)"; cmd.Parameters.Add(new SqlParameter("@newval", DateTime.UtcNow)); cmd.ExecuteNonQuery(); cmd.CommandText = "SELECT val FROM testtbl"; var result = (DateTime)cmd.ExecuteScalar(); Console.WriteLine("Kind: " + result.Kind); Console.WriteLine("ToString(): " + result.ToString()); Console.WriteLine("Add 1 minute, is greater than UtcNow? " + (result.AddMinutes(1) > DateTime.UtcNow).ToString()); Console.WriteLine("Add 1 minute, is greater than Now? " + (result.AddMinutes(1) > DateTime.Now).ToString()); Console.WriteLine("Add 1 minute, is less than UtcNow? " + (result.AddMinutes(1) < DateTime.UtcNow).ToString()); Console.WriteLine("Add 1 minute, is less than Now? " + (result.AddMinutes(1) < DateTime.Now).ToString()); Console.WriteLine("Subtract 1 minute, is greater than UtcNow? " + (result.AddMinutes(-1) > DateTime.UtcNow).ToString()); Console.WriteLine("Subtract 1 minute, is greater than Now? " + (result.AddMinutes(-1) > DateTime.Now).ToString()); Console.WriteLine("Subtract 1 minute, is less than UtcNow? " + (result.AddMinutes(-1) < DateTime.UtcNow).ToString()); Console.WriteLine("Subtract 1 minute, is less than Now? " + (result.AddMinutes(-1) < DateTime.Now).ToString());
Result:
Kind: Unspecified ToString(): 6/3/2010 10:17:05 PM Add 1 minute, is greater than UtcNow? True Add 1 minute, is greater than Now? True Add 1 minute, is less than UtcNow? False Add 1 minute, is less than Now? False Subtract 1 minute, is greater than UtcNow? False Subtract 1 minute, is greater than Now? True Subtract 1 minute, is less than UtcNow? True Subtract 1 minute, is less than Now? False
Compare this with DateTime.Now:
cmd.CommandText = "INSERT INTO testtbl (val) VALUES (@newval)"; cmd.Parameters.Add(new SqlParameter("@newval", DateTime.Now)); cmd.ExecuteNonQuery(); cmd.CommandText = "SELECT val FROM testtbl"; var result = (DateTime)cmd.ExecuteScalar(); Console.WriteLine("Kind: " + result.Kind); Console.WriteLine("ToString(): " + result.ToString()); Console.WriteLine("Add 1 minute, is greater than UtcNow? " + (result.AddMinutes(1) > DateTime.UtcNow).ToString()); Console.WriteLine("Add 1 minute, is greater than Now? " + (result.AddMinutes(1) > DateTime.Now).ToString()); Console.WriteLine("Add 1 minute, is less than UtcNow? " + (result.AddMinutes(1) < DateTime.UtcNow).ToString()); Console.WriteLine("Add 1 minute, is less than Now? " + (result.AddMinutes(1) < DateTime.Now).ToString()); Console.WriteLine("Subtract 1 minute, is greater than UtcNow? " + (result.AddMinutes(-1) > DateTime.UtcNow).ToString()); Console.WriteLine("Subtract 1 minute, is greater than Now? " + (result.AddMinutes(-1) > DateTime.Now).ToString()); Console.WriteLine("Subtract 1 minute, is less than UtcNow? " + (result.AddMinutes(-1) < DateTime.UtcNow).ToString()); Console.WriteLine("Subtract 1 minute, is less than Now? " + (result.AddMinutes(-1) < DateTime.Now).ToString());
Done at 15:58 (local, -7h):
Kind: Unspecified ToString(): 6/3/2010 3:59:26 PM Add 1 minute, is greater than UtcNow? False Add 1 minute, is greater than Now? True Add 1 minute, is less than UtcNow? True Add 1 minute, is less than Now? False Subtract 1 minute, is greater than UtcNow? False Subtract 1 minute, is greater than Now? False Subtract 1 minute, is less than UtcNow? True Subtract 1 minute, is less than Now? True