This is a very common anti-pattern:
string dateTime = reader["dte_tme"].ToString();
DateTime dt = Convert.ToDateTime(dateTime);
The correct incantation is as follows:
DateTime dt = (DateTime) reader["dte_tme"];
While the return type of reader["dte_time"]
is an object
, that object contains a DateTime
. If you set a breakpoint, you'd see the DateTime
is already there. You just need to cast it so it can be assigned to a DateTime
variable. This is called unboxing.
If the datetime
column in the SQL database is nullable, then you should test for that like this:
DateTime? dt = reader["dte_tme"] == DBNull.Value ? null : (DateTime) reader["dte_tme"];
Or sometimes you will see it like this, which is equally acceptable:
DateTime? dt = reader["dte_tme"] as DateTime?;
It absolutely does not need to be treated as a string at any point when retrieving it from the database. If it's a datetime
in the database, then it's a DateTime
in C#.
You should use a casting operation when pulling data from a datareader, even with other data types such as integers, decimals, and even strings. You can see other type mappings between SQL Server data types and .NET data types in the chart here.
Now with regard to time zone, that's a different issue. First, understand that DateTime
doesn't keep a time zone. It only has knowledge of the DateTimeKind
it is assigned. By default, the kind is Unspecified
, which essentially means, "I don't know; it could be anything".
That said, different protocols have different requirements. JSON has no predefined format for dates, but the most common convention (and best practice) is to store a date in ISO8601 format, which is YYYY-MM-DDTHH:mm:ss
. Time zone information is optional, and will usually not be included when the .Kind
of a DateTime
is DateTimeKind.Unspecified
. If it were Utc
, then you would see a Z
at the end, and if it were Local
, then you would see an offset of the local time zone, such as +11:00
. That is, whatever offset is appropriate for that time zone, at that particular moment. An offset is not the same thing as a "time zone", because different offset could apply within the same time zone at different times - usually to daylight saving time.
XML is a bit different. Most of the XML serialization in .NET will use the W3C XML Schema specification, and will map a DateTime
to an xsd:dateTime
type. How exactly it is rendered will depend on the Kind
.
- For
DateTimeKind.Unspecified
, it will not include an offset.
- For
DateTimeKind.Utc
, it will append a Z
- For
DateTimeKind.Local
, it will append the local offset
You asked why the Kind
is Local
when you look at it in the dataset? That's because DataSet
has an ugly behavior of assuming that all times are local. It essentially ignores the .Kind
property and assumes the behavior of DateTimeKind.Local
. This is a longstanding bug.
Ideally, you would use a datetimeoffset
type in SQL Server, and a DateTimeOffset
type in .NET. This avoids the "kind" issues, and serializes nicely in JSON (when you use modern serializers like JSON.NET). In XML, however, it should get mapped to xsd:dateTime
and rendered just like the local DateTime
did, just with the correct offset. However it instead ends up looking like this:
<Value xmlns:d2p1="http://schemas.datacontract.org/2004/07/System">
<d2p1:DateTime>2015-03-18T03:34:11.3097587Z</d2p1:DateTime>
<d2p1:OffsetMinutes>-420</d2p1:OffsetMinutes>
</Value>
That's with DataContractXmlSerializer
. If you use the XmlSerializer
, you it can't render at all. You just get an empty node, such as <Value/>
.
However, even with all of that said, you said you were using DataSet
, and that comes with it's own set of behaviors. On the bad side, it will assume that all DateTime
values have DateTimeKind.Local
- even when they don't, as I mentioned above. Consider the following:
DataTable dt = new DataTable();
dt.Columns.Add("Foo", typeof (DateTime));
dt.Rows.Add(new DateTime(2015, 1, 1, 0, 0, 0, DateTimeKind.Unspecified));
dt.Rows.Add(new DateTime(2015, 1, 1, 0, 0, 0, DateTimeKind.Local));
dt.Rows.Add(new DateTime(2015, 1, 1, 0, 0, 0, DateTimeKind.Utc));
DataSet ds = new DataSet();
ds.Tables.Add(dt);
string xml = ds.GetXml();
Debug.Write(xml);
This is the output when I run it (in the US Pacific time zone):
<NewDataSet>
<Table1>
<Foo>2015-01-01T00:00:00-08:00</Foo>
</Table1>
<Table1>
<Foo>2015-01-01T00:00:00-08:00</Foo>
</Table1>
<Table1>
<Foo>2015-01-01T00:00:00-08:00</Foo>
</Table1>
</NewDataSet>
However, the good news is that DateTimeOffset
values are a little better:
DataTable dt = new DataTable();
dt.Columns.Add("Foo", typeof(DateTimeOffset));
dt.Rows.Add(new DateTimeOffset(2015, 1, 1, 0, 0, 0, TimeSpan.FromHours(11)));
dt.Rows.Add(new DateTimeOffset(2015, 1, 1, 0, 0, 0, TimeSpan.Zero));
dt.Rows.Add(new DateTimeOffset(2015, 1, 1, 0, 0, 0, TimeSpan.FromHours(-3)));
DataSet ds = new DataSet();
ds.Tables.Add(dt);
string xml = ds.GetXml();
Debug.Write(xml);
Output:
<NewDataSet>
<Table1>
<Foo>2015-01-01T00:00:00+11:00</Foo>
</Table1>
<Table1>
<Foo>2015-01-01T00:00:00Z</Foo>
</Table1>
<Table1>
<Foo>2015-01-01T00:00:00-03:00</Foo>
</Table1>
</NewDataSet>
For the most part, this is correct, though technically it should have serialized the second one using +00:00
instead of Z
, but that's not going to matter all that much in practice.
The last thing I'd just like to say is that in general, DataSet
is a relic from the past. In modern development, there should be very little need to use it in your day to day code. If possible, I would seriously consider exploring other options.