poniedziałek, 17 listopada 2008

Data jest data?!

Operacje na datach to pewnie chleb powszedni dla każdego programisty. Wstawianie ich do bazy danych, to kolejna, seryjna nasza czynność. W zasadzie bardziej chodzi mi o umieszczanie domyślnych wartości daty w tabelach bazy danych MS SQL Server. W sumie niebyłoby nic odkrywczego, gdyby nie fakt, że wyjątki w takim kodzie pojawiają się w najmniej oczekiwanych momentach :-)

Istnieje spora różnica między wartościami: DateTime.MinValue, który w rezultacie da: 01-01-0001, a SqlDateTime.MinValue, który zwróci: 01-01-1753. Cała zabawa powoduję, że podczas próby wstawienia rekordu, którego wartość w kolumnie typu DateTime jest generowana w kodzie programu przez DateTime.MinValue otrzymujemy wyjątek przekroczenia wartości. Dlatego, aby wstawić minimalną wartość typu DateTime do bazy danych należy zawsze posługiwać się SqlDateTime.MinValue.

Jeszcze ciekawsza systuacja jest przy porównaniu wartości: DateTime.MaxValue oraz SqlDateTime.MaxValue. Zróbmy proste zadanie: wstawmy do tabeli w naszej bazie danych wartość DateTime.MaxValue, potem odczytajmy tą wartość do zmiennej lokalnej tego samego typu. Na szybko naskrobałem coś takiego:

static void Main(string[] args)
{
    DateTime fromDb = DateTime.MaxValue;
    DateTime toDb = DateTime.MaxValue;
    SqlConnection conn = new SqlConnection(@"Data Source=TOSHIBA\SQLEXPRESS;Initial Catalog=Poligon;Integrated Security=True");
    try
    {
        conn.Open();
        SqlCommand cmd = new SqlCommand("INSERT INTO Daty VALUES(@data)", conn);
        cmd.Parameters.Add("data", System.Data.SqlDbType.DateTime).Value = DateTime.MaxValue;
        cmd.ExecuteNonQuery();
        SqlCommand getCmd = new SqlCommand("SELECT TOP(1) DAT_Data FROM Daty", conn);
        using (IDataReader rdr = getCmd.ExecuteReader())
        {
            while(rdr.Read())
            {
                fromDb = Convert.ToDateTime(rdr["DAT_Data"]);
            }
        }
        bool areSame = (DateTime.Compare(toDb, fromDb) == 0);
        TimeSpan subDates = toDb.Subtract(fromDb);
        int result = subDates.Milliseconds; //nasz wynik
    }
    finally
    {
        conn.Close();
        conn.Dispose();
    }
}

Że co?


Metoda DateTime.Compare nie zwróciła nam wartości 0 (zero), co oznaczałoby, że data zapisana do bazy i ta odczytana są takie same. Zwróciła nam wartość, która oświadcza nam, że wartość otrzymana z bazy danych jest mniejsza od tej, którą zapisaliśmy.


Zaglądamy do zmiennej result...


Zmienna result trzyma dla nas różnicę obu dat. Po odjęciu wartości otrzymanej z bazy danych od tej którą zapisywaliśmy dostajemy różnicę wynoszącą... dwie równiutkie milisekundy.


Prawie robi wielką różnicę


DateTime.MaxValue to: 31-12-9999 23:59:59.999, natomiast maksymalna wartość daty dla bazy danych, czyli to co zwraca SqlDateTime.MaxValue to: 31-12-9999 23:59:59.997 Różnica dwóch milisekund występuje w momencie konwersji DateTime do SqlDateTime, tu nie otrzymujemy wyjątku - a powinniśmy gdyż zakresy nieznacznie, bo nieznacznie, ale różnią się. Baza danych posłusznie przyjmuje wartość potajemnie ucinając 2 ms. Biednemu developerowi wydaje się że wszystko jest ok (bo narzędzia podglądu danych oraz debugger nie wyświetlają wartości z tak dużą precyzją). Pytanie za 100 punktów: co powodują taką różnice? Dlaczego nie można było tej wartości wydłużyć o te 2 nieszczęsne milisekundy?

2 komentarze:

Anonimowy pisze...

Dlaczego nie można było tej wartości wydłużyć o te 2 nieszczęsne milisekundy?

Odpowiedz jest prosta SqlDateTime(a dokładniej datetime sqlowy) ma dokładność do 3 tysięcznych sekundy, konsekwencje łatwo wywnioskować.

więcej na :
http://msdn.microsoft.com/en-us/library/aa258277(SQL.80).aspx

Dariusz Tarczyński pisze...

Ok, brawo za odpowiedź!