0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

DataTableのDateTime型の列について

Last updated at Posted at 2018-09-12

データベースの日付型列とDateTimeのKindについて でDateTimeのKindの状態によって、PostgreSQLのTIMESTAMP WITH TIME ZONE型の列にどのように書き込まれるかを確認した。

今度は、データベースのテーブルをDataTableに取り込んだとき、日付型のデータがどうなるか確認してみる。

環境は前回に引き続き
Visual Studio 2017
Npgsql 4.0.2
PostgreSQL 9.2.4
で検証してみた。

現在、データベースにtable1というテーブルがあり、データは次のようになっているとする。

id datetime_tz datetime
1 2018-01-01 00:00:00+09 2018-01-01 00:00:00
2 2018-01-01 09:00:00+09 2018-01-01 00:00:00
3 2018-01-01 09:00:00+09 2018-01-01 00:00:00
検証ソース
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data;
using Npgsql;

namespace DateTimeOffsetTestNpgsql
{
    class Program
    {
        static void Main(string[] args)
        {
            NpgsqlConnection conn = new NpgsqlConnection("Server=xxx.xxx.xxx.xxx;User Id=xxxx;Password=xxxx;Database=xxxx;Pooling=false;");
            NpgsqlDataAdapter da = new NpgsqlDataAdapter();
            da.SelectCommand = new NpgsqlCommand("SELECT id, datetime_tz, datetime FROM table1 ORDER BY id", conn);
            conn.Open();
            DataTable table1 = new DataTable();
            da.Fill(table1);
            foreach (DataRow row in table1.Rows)
            {
                var id          = row["id"         ];
                var datetime_tz = row["datetime_tz"];
                var datetime    = row["datetime"   ];
                Console.WriteLine("id="          + id.ToString());
                Console.WriteLine("datetime_tz=" + datetime_tz.ToString() + " Kind=" + ((DateTime)datetime_tz).Kind.ToString());
                Console.WriteLine("datetime   =" + datetime   .ToString() + " Kind=" + ((DateTime)datetime   ).Kind.ToString());
                Console.WriteLine();
            }
            conn.Close();

            Console.ReadKey();
        }
    }
}

結果は

id=1
datetime_tz=2018/01/01 0:00:00 Kind=Unspecified
datetime   =2018/01/01 0:00:00 Kind=Unspecified

id=2
datetime_tz=2018/01/01 9:00:00 Kind=Unspecified
datetime   =2018/01/01 0:00:00 Kind=Unspecified

id=3
datetime_tz=2018/01/01 9:00:00 Kind=Unspecified
datetime   =2018/01/01 0:00:00 Kind=Unspecified

となり、DateTimeのKindが全てUnspecifiedとなった

NpgsqlDataReaderで読み込んだときには

  • DATETIME WITH TIME ZONE型はKind=Local
  • DATETIME WITHOUT TIME ZONE型はKind=Unspecified

となっていたが、DataTableに読み込むとうまくKindまで設定してくれていない。

今度は、DataTableにデータを入れて、DataAdapter.Updateでデータベースに書き込んでみる。

検証ソース
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data;
using Npgsql;

namespace DateTimeOffsetTestNpgsql
{
    class Program
    {
        static void Main(string[] args)
        {
            DateTime dateTimeLocal       = new DateTime(2018, 1, 1, 0, 0, 0, DateTimeKind.Local      );
            DateTime dateTimeUtc         = new DateTime(2018, 1, 1, 0, 0, 0, DateTimeKind.Utc        );
            DateTime dateTimeUnspecified = new DateTime(2018, 1, 1, 0, 0, 0, DateTimeKind.Unspecified);

            NpgsqlConnection conn = new NpgsqlConnection("Server=xxx.xxx.xxx.xxx;User Id=xxxx;Password=xxxx;Database=xxxx;Pooling=false;");
            NpgsqlDataAdapter da = new NpgsqlDataAdapter();
            da.SelectCommand = new NpgsqlCommand("SELECT id, datetime_tz, datetime FROM table1 ORDER BY id", conn);
            da.InsertCommand = new NpgsqlCommand("INSERT INTO table1 (id, datetime_tz, datetime) VALUES (:id, :datetime_tz, :datetime)", conn);
            da.InsertCommand.Parameters.Add(new NpgsqlParameter("id"         , NpgsqlTypes.NpgsqlDbType.Integer    , 0, "id"         ));
            da.InsertCommand.Parameters.Add(new NpgsqlParameter("datetime_tz", NpgsqlTypes.NpgsqlDbType.TimestampTz, 0, "datetime_tz"));
            da.InsertCommand.Parameters.Add(new NpgsqlParameter("datetime"   , NpgsqlTypes.NpgsqlDbType.Timestamp  , 0, "datetime"   ));

            DataTable table1 = new DataTable();
            table1.Columns.Add(new DataColumn("id"         , typeof(int)     ));
            table1.Columns.Add(new DataColumn("datetime_tz", typeof(DateTime)));
            table1.Columns.Add(new DataColumn("datetime"   , typeof(DateTime)));
            DataRow row1 = table1.NewRow();
            row1["id"         ] = 1;
            row1["datetime_tz"] = dateTimeLocal;
            row1["datetime"   ] = dateTimeLocal;
            table1.Rows.Add(row1);
            DataRow row2 = table1.NewRow();
            row2["id"         ] = 2;
            row2["datetime_tz"] = dateTimeUtc;
            row2["datetime"   ] = dateTimeUtc;
            table1.Rows.Add(row2);
            DataRow row3 = table1.NewRow();
            row3["id"         ] = 3;
            row3["datetime_tz"] = dateTimeUnspecified;
            row3["datetime"   ] = dateTimeUnspecified;
            table1.Rows.Add(row3);
            Console.WriteLine("row1[id         ]=" + ((int)row1["id"]).ToString());
            Console.WriteLine("row1[datetime_tz]=" + ((DateTime)row1["datetime_tz"]).ToString() + " Kind=" + ((DateTime)row1["datetime_tz"]).Kind.ToString());
            Console.WriteLine("row1[datetime   ]=" + ((DateTime)row1["datetime"   ]).ToString() + " Kind=" + ((DateTime)row1["datetime"   ]).Kind.ToString());
            Console.WriteLine("row2[id         ]=" + ((int)row2["id"]).ToString());
            Console.WriteLine("row2[datetime_tz]=" + ((DateTime)row2["datetime_tz"]).ToString() + " Kind=" + ((DateTime)row2["datetime_tz"]).Kind.ToString());
            Console.WriteLine("row2[datetime   ]=" + ((DateTime)row2["datetime"   ]).ToString() + " Kind=" + ((DateTime)row2["datetime"   ]).Kind.ToString());
            Console.WriteLine("row3[id         ]=" + ((int)row3["id"]).ToString());
            Console.WriteLine("row3[datetime_tz]=" + ((DateTime)row3["datetime_tz"]).ToString() + " Kind=" + ((DateTime)row3["datetime_tz"]).Kind.ToString());
            Console.WriteLine("row3[datetime   ]=" + ((DateTime)row3["datetime"   ]).ToString() + " Kind=" + ((DateTime)row3["datetime"   ]).Kind.ToString());
            Console.WriteLine();

            conn.Open();

            using (NpgsqlCommand cmd = new NpgsqlCommand("DELETE FROM table1", conn))
            {
                cmd.ExecuteNonQuery();
            }

            da.Update(table1);

            DataTable table2 = table1.Clone();
            da.Fill(table2);
            foreach (DataRow row in table2.Rows)
            {
                var id          = row["id"         ];
                var datetime_tz = row["datetime_tz"];
                var datetime    = row["datetime"   ];
                Console.WriteLine("id="          + id.ToString());
                Console.WriteLine("datetime_tz=" + datetime_tz.ToString() + " Kind=" + ((DateTime)datetime_tz).Kind.ToString());
                Console.WriteLine("datetime   =" + datetime   .ToString() + " Kind=" + ((DateTime)datetime   ).Kind.ToString());
                Console.WriteLine();
            }
            conn.Close();

            Console.ReadKey();
        }
    }
}

結果、データベースのテーブル table1 は

id datetime_tz datetime
1 2018-01-01 09:00:00+09 2018-01-01 00:00:00
2 2018-01-01 09:00:00+09 2018-01-01 00:00:00
3 2018-01-01 09:00:00+09 2018-01-01 00:00:00
となり、実行結果の出力は
row1[id         ]=1
row1[datetime_tz]=2018/01/01 0:00:00 Kind=Unspecified
row1[datetime   ]=2018/01/01 0:00:00 Kind=Unspecified
row2[id         ]=2
row2[datetime_tz]=2018/01/01 0:00:00 Kind=Unspecified
row2[datetime   ]=2018/01/01 0:00:00 Kind=Unspecified
row3[id         ]=3
row3[datetime_tz]=2018/01/01 0:00:00 Kind=Unspecified
row3[datetime   ]=2018/01/01 0:00:00 Kind=Unspecified

id=1
datetime_tz=2018/01/01 9:00:00 Kind=Unspecified
datetime   =2018/01/01 0:00:00 Kind=Unspecified

id=2
datetime_tz=2018/01/01 9:00:00 Kind=Unspecified
datetime   =2018/01/01 0:00:00 Kind=Unspecified

id=3
datetime_tz=2018/01/01 9:00:00 Kind=Unspecified
datetime   =2018/01/01 0:00:00 Kind=Unspecified

となった。

どうやら、DataTableのDateTime型の列にDateTimeのKindがLocalやUtcのデータを入れても、DataTable側の列はKind=Unspecifiedのままのようだ。
そのため、Npgsql 3.x系以降では前回検証したように、

  • Kind=Unspecifiedは、UTCから日本時間に変換されて書き込まれる。

ので、データベースには2018-01-01 09:00:00+09が書き込まれている。
Npgsql 2.x系の場合は

  • Kind=Unspecifiedは、日本時間で書き込まれる。

だったので、おそらくデータベースには2018-01-01 00:00:00+09で書き込まれるはずである(あとで検証してみた)。

なんで、DataRowのDateTime型の列にDateTime型の変数を渡したときに、Kindが引き継がれないの?という疑問が残るけど、行毎にKindが異なるとマズイんだろうなーっということは想像できる。

これだとDataTableとDataAdapterを使って、ちゃんと日時データを取り扱えない。。。困った。
※Npgsql 2.x系だとUnspecifiedがLocalと同じ挙動なので、なんとなく思ったように動くけど、3.x以降だとダメじゃん。

途方に暮れながら更に調べてみると、DataColumnにDateTimeModeというプロパティがあることを発見した!
DataColumn.DateTimeMode プロパティ
DataSetDateTime 列挙型

DataTableのDateTime型の列のDateTimeModeをLocalに変更してやればいいのではないか?
ということで、上記検証ソースのtable1の定義後に

追加
            table1.Columns["datetime_tz"].DateTimeMode = DataSetDateTime.Local;

を追加して実行してみると、データベースのtable1は

id datetime_tz datetime
1 2018-01-01 00:00:00+09 2018-01-01 00:00:00
2 2018-01-01 09:00:00+09 2018-01-01 00:00:00
3 2018-01-01 00:00:00+09 2018-01-01 00:00:00
となり、実行結果の出力は
row1[id         ]=1
row1[datetime_tz]=2018/01/01 0:00:00 Kind=Local
row1[datetime   ]=2018/01/01 0:00:00 Kind=Unspecified
row2[id         ]=2
row2[datetime_tz]=2018/01/01 9:00:00 Kind=Local
row2[datetime   ]=2018/01/01 0:00:00 Kind=Unspecified
row3[id         ]=3
row3[datetime_tz]=2018/01/01 0:00:00 Kind=Local
row3[datetime   ]=2018/01/01 0:00:00 Kind=Unspecified

id=1
datetime_tz=2018/01/01 0:00:00 Kind=Local
datetime   =2018/01/01 0:00:00 Kind=Unspecified

id=2
datetime_tz=2018/01/01 9:00:00 Kind=Local
datetime   =2018/01/01 0:00:00 Kind=Unspecified

id=3
datetime_tz=2018/01/01 0:00:00 Kind=Local
datetime   =2018/01/01 0:00:00 Kind=Unspecified

となり、狙った通りの動作になった。

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?