2010-11-29 26 views
9

J'ai rencontré quelque chose qui semble étrange. SQL Server semble arrondir certaines valeurs DateTime de manière inappropriée lorsque je les enregistre dans des colonnes datetime. Je soupçonne qu'il me manque quelque chose, mais je ne le vois pas. J'exécute ce test sur SQL Server 2008 en utilisant .NET 4.0. Les éléments suivants doivent illustrer le problème:Arrondi indésirable de DateTime dans SQL Server

J'ai créé une table dans SQL Server appelée Timestamps. Il dispose de deux colonnes:

id - bigint, Identité, PK
timestamp - datetime

J'ai aussi créé un test simple qui effectue les opérations suivantes:

  1. Obtient l'heure actuelle , tronquer la valeur à la milliseconde précision
  2. sauvé le temps tronqué à Timestamps Récupération de la valeur datetime dans la base de données et comparaison avec l'objet DateTime d'origine (tronqué).
public static void RoundTest() 
{ 
    DateTime preTruncation = DateTime.UtcNow; 
    DateTime truncated = preTruncation.TruncateToMilliseconds(); 

    using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["test"].ConnectionString)) 
    { 
     conn.Open(); 
     SqlCommand cmd = new SqlCommand(@"INSERT INTO Timestamps(timestamp) 
              VALUES(@savedTime); 
              SELECT SCOPE_IDENTITY() AS id"); 
     cmd.Parameters.Add(new SqlParameter("savedTime", truncated)); 
     cmd.Connection = conn; 
     var id = cmd.ExecuteScalar(); 

     SqlCommand get = new SqlCommand(@"SELECT timestamp FROM Timestamps 
              WHERE id = @id"); 

     get.Parameters.Add(new SqlParameter("id", id)); 
     get.Connection = conn; 
     DateTime retrieved = (DateTime)get.ExecuteScalar(); 

     if (retrieved != truncated) 
     { 
      Console.WriteLine("original: " + preTruncation.TimeOfDay); 
      Console.WriteLine("truncated: " + truncated.TimeOfDay); 
      Console.WriteLine("retrieved: " + retrieved.TimeOfDay); 
      Console.WriteLine(); 
     } 
    } 
} 

Bien que j'attends la valeur tronquée soit équivalente à la valeur renvoyée en retrait de la DB, ce n'est pas toujours le cas. Voici quelques exemple de sortie:

original: 19:59:13.4049965 
truncated: 19:59:13.4040000 
retrieved: 19:59:13.4030000 

original: 19:59:14.4989965 
truncated: 19:59:14.4980000 
retrieved: 19:59:14.4970000 

original: 19:59:15.4749965 
truncated: 19:59:15.4740000 
retrieved: 19:59:15.4730000 

original: 19:59:30.1549965 
truncated: 19:59:30.1540000 
retrieved: 19:59:30.1530000 

TruncateToMilliseconds() ressemble à ceci:

public static DateTime TruncateToMilliseconds(this DateTime t) 
{ 
    return new DateTime(t.Year, t.Month, t.Day, t.Hour, t.Minute, t.Second, t.Millisecond); 
} 

Qu'est-ce qui se passe? Est-ce vraiment un arrondissement inapproprié, ou est-ce que je fais une supposition erronée ici?

Répondre

13

Datetime est seulement précis à 3ms. Par conséquent, il arrondira au multiple de 3 ms le plus proche. Pour surmonter cela, regardez le datetime2. Notez que ceci est pour SQL2008 + seulement

EDIT: ce n'est pas tout à fait à 3ms. Il est arrondi à des incréments de .000, .003 ou .007 secondes

+1

En fait, il stocke un datetime dans deux entiers de 4 octets, le premier représentant la date, et le second entier de 4 octets représentant le nombre de ticks d'horloge depuis minuit. (environ 3,33 millisecondes chacun) –

+0

Correct. Finit par être .003, .007 ou .000 –

+0

Merci! J'ai juste supposé que datetime était précis à 1ms, puisqu'il a une précision de 1ms. – Odrade