C#でCSVファイルを読み書きする場合、CsvHelper
が便利なのでNuGetからインストールして使っています。
JoshClose/CsvHelper: Library to help reading and writing CSV files
そんな中、
name,date1,date2,date3 hoge,20160101,20160102,20160103
のようなCSVファイルにて、日付っぽい値(yyyyMMdd
)をC#のDateTime構造体にマッピングする必要があったので、メモを残します。
環境
- Windows10
- Visual Studio2015 Update1
- .NET Framework 4.6.1
- CsvHelper 2.13.5
実装の流れ
今回、
という形で進めます。
マッピング先のクラスを作成
public class CsvFile { public string Name { get; set; } public DateTime Date1 { get; set; } public DateTime Date2 { get; set; } public DateTime Date3 { get; set; } }
マッピング用のクラスを作成
CSVファイルと上記のCsvFile
クラスをマッピングするためのクラスを作成します。
通常であれば、
public class Mapper : CsvHelper.Configuration.CsvClassMap<CsvFile> { public Mapper() { Map(m => m.Name).Index(0); Map(m => m.Date1).Index(1); ... } }
とすれば、
となります。
ただ、このまま実行すると、Date1へのマッピングで文字列は有効な DateTime ではありませんでした。
というエラーで動作しません。CSVファイルのデータが20160101
と、日付っぽく見えて日付ではない値になっているためです。
そのため、いくつかの方法を使って、日付っぽい値とDateTime構造体のマッピングを行ってみます。
カスタムタイプコンバータを作成する方法
公式ドキュメントのWikiにある、カスタムタイプコンバータを使ってマッピングしてみます。
Custom TypeConverter · JoshClose/CsvHelper Wiki
WikiではDefaultTypeConverter
を継承したクラスを作成・使用していますが、ソースコードを読んでみたところ、DefaultTypeConverterを継承したDateTimeConverter
がありました。
CsvHelper/DateTimeConverter.cs at master · JoshClose/CsvHelper
そのため、今回はDateTimeConverterを継承したカスタムタイプコンバータを作成・使用します。
public class CsvDateConverter : CsvHelper.TypeConversion.DateTimeConverter { public override object ConvertFromString(CsvHelper.TypeConversion.TypeConverterOptions options, string text) { if (text == null) { return base.ConvertFromString(options, null); } if (text.Trim().Length == 0) { return DateTime.MinValue; } return DateTime.ParseExact(text, "yyyyMMdd", null); } }
作成したカスタムタイプコンバータは
Map(m => m.Date1).Index(1).TypeConverter<CsvDateConverter>();
のように使います。
ConvertUsing()を使う方法
カスタムタイプコンバータでは別途クラスを用意する必要があったため、今回のような単純な例で使うのは少々手間でした。
そこで、ConvertUsing()
を使ったマッピングを試してみます。
CsvHelper - Convert Using
ConvertUsingのパラメータ(ここではrow
という名前)に、CSVファイルの一行分のデータが入っています。そのため、GetField()
メソッドにCSVファイルの列インデックスを渡すことで、列を指定してマッピングできます。
CsvHelper - Reading individual fields
Map(m => m.Date2).ConvertUsing(row => DateTime.ParseExact(row.GetField<string>(2), "yyyyMMdd", null));
TypeConverterOption()を使う方法
ConvertUsing()
を使っても少々手間なので、再度CsvHelper.TypeConversion.DateTimeConverter
クラスを眺めてみます。
すると、ConvertFromString()
メソッドの引数で受け取ったTypeConverterOptions.Format
を使い、DateTime構造体へと変換・マッピングしていました。
CsvHelper/DateTimeConverter.cs - JoshClose/CsvHelper
TypeConverterOptions.Formatはどこで設定するのかをたどっていくと、CsvHelper.Configuration.CsvPropertyMap.TypeConverterOption()
メソッドがありました。TypeConverterOption()にはオーバーロードがありましたが、string型で渡すのが良さそうでした。
CsvHelper/CsvPropertyMap.cs at JoshClose/CsvHelper
以上より、TypeConverterOption()
メソッドを使って、変換時に使うフォーマットを文字列で指定してマッピングします。
CsvHelper - Type Converter Options
Map(m => m.Date3).Index(3).TypeConverterOption("yyyyMMdd");
結果確認
こんな感じの
static void Main(string[] args) { var runDir = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); var path = System.IO.Path.Combine(runDir, @"test.csv"); using (var sr = new System.IO.StreamReader(path)) using (var csv = new CsvHelper.CsvReader(sr)) { csv.Configuration.RegisterClassMap<Mapper>(); var records = csv.GetRecords<CsvFile>(); foreach (var r in records) { Console.WriteLine($"date1: {r.Date1}\ndate2: {r.Date3}\ndate3: {r.Date3}"); } Console.ReadKey(); } }
コンソールアプリを作成して実行したところ、
date1: 2016/01/01 0:00:00 date2: 2016/01/03 0:00:00 date3: 2016/01/03 0:00:00
のように表示され、いずれのパターンでもマッピングができていました。
ソースコード
GitHubに上げました。DateConverterSample
プロジェクトが今回のソースコードです。
thinkAmi-sandbox/CsvHelperSample