Jeg var måske lidt forvirret da jeg skrev min post om datoer i går, så jeg kommer lige med en specificering af mine undersøgelser. Jeg tager udgangspunkt i følgende lille konsol app:
var sel = new SqlCommand("SELECT TOP 1 * FROM DatoTabel order by id desc", forbindelse);
var insert = new SqlCommand("INSERT INTO DatoTabel (Dato) VALUES (@dato)", forbindelse);
var dato = new DateTime(2009, 9, 21, 12, 0, 0);
Console.WriteLine("DateTime instans");
Console.WriteLine("Værdi: {0:o}", dato);
Console.WriteLine("Kind: {0}", dato.Kind);
Console.WriteLine("====================");
insert.Parameters.AddWithValue("@dato", dato);
forbindelse.Open();
insert.ExecuteNonQuery();
using (var r = sel.ExecuteReader())
if (r.Read())
dato = r.GetDateTime(1);
forbindelse.Close();
Console.WriteLine("SQL roundtripped instans");
Console.WriteLine("Værdi: {0:o}", dato);
Console.WriteLine("Kind: {0}", dato.Kind);
Den giver følgende output:
Her skal man bemærke at variablen: dato, bliver instantieret til et specifik dato-klokkeslæt. Kind er som default Unspecified, og SQL roundtrippet returnerer også Unspecified. Helt som man kunne forvente (men var det det man forventede?).
Hvis vi prøver med den overload, hvor man kan specificerer Kind:
var dato = new DateTime(2009, 9, 21, 12, 0, 0, DateTimeKind.Local);
// DateTime.Now har også Kind=Local
Så får man følgende overraskende resultat:

Vi gemmer en dato med tids-zone, men får en “DK-UTC” værdi tilbage – altså det samme klokkeslæt, men man har mistet zone informationen. Nu er man altså overladt til konventioner, når man hiver datoer ud af databasen. Det viser sig at vi helt upåagtet har en konvention der siger:
Når vi gemmer en DateTime i databasen, så gemmer vi den med dansk zone-offset som udgangspunkt.
Man bør derfor rette sin udlæsningskode til på følgende måde, når man har den konvention:
dato = DateTime.SpecifyKind(r.GetDateTime(1), DateTimeKind.Local);
Det vil give følgende output:
Så er der styr på zonen… så længe klienten har dansk som lokal indstilling… Man kan altså få overraskende resultater, hvis man f.eks. har en webserver der står i USA, og man bruger ovenstående konvention, for så bliver man offsat med 6 timer.
Hvis man starter på en ny database, vil den rigtige løsning være at vedtage en konvention, der siger at alle DateTime værdier der gemmes i databasen, skal tolkes som UTC. På den måde vil man ikke blive snydt af klienter/servere med forskellige zoner. Man skal så sørge for at konverterer DateTime objektet inden man gemmer, og så skal man specificerer Utc når man henter:
Console.WriteLine("DateTime instans");
Console.WriteLine("Værdi: {0:o}", dato);
Console.WriteLine("Kind: {0}", dato.Kind);
Console.WriteLine("====================");
insert.Parameters.AddWithValue("@dato", dato.ToUniversalTime());
forbindelse.Open();
insert.ExecuteNonQuery();
using (var r = sel.ExecuteReader())
if (r.Read())
dato = DateTime.SpecifyKind(r.GetDateTime(1), DateTimeKind.Utc);
forbindelse.Close();
Det giver følgende output:

Nu kan ma så lade klienten tage stilling til hvilken tids-zone der skal vises, og man kan kalde ToLocalTime metoden, hvis man vil udlæse i den aktuelle tids-zone.
Alternativt, kan man vælge en af de nye felttyper i SQL Server 2008, hvor man har DateTimeOffset, som jo gemmer zone info. Husk bare at en konvertering fra DateTime, skal tage højde for den implicitte konvention på de eksisterende felter.