DataTableのDateTime型の列について

[データベースの日付型列とDateTimeのKindについて](https://qiita.com/kkino90h/items/e6be31d990af8ff22b90) で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 |

```c#:検証ソース
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でデータベースに書き込んでみる。

```c#:検証ソース
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 プロパティ](https://msdn.microsoft.com/ja-jp/library/system.data.datacolumn.datetimemode%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396)
[DataSetDateTime 列挙型](https://msdn.microsoft.com/ja-jp/library/system.data.datasetdatetime(v=vs.110).aspx)

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

```c#:追加
            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
```
となり、狙った通りの動作になった。