I have a model that has a DateTime type property. From this property, I want to include the seconds and microseconds to the string when writing to CSV file and when reading from the CSV file through CsvHelper.
So here is how I came up with the solution by extending the DefaultTypeConverter
public class CsvDateTimeConverter : DefaultTypeConverter
{
private const string DateStringFormat = "MM/dd/yyyy HH:mm:ss.fff";
public override object ConvertFromString(TypeConverterOptions options, string text)
{
if (string.IsNullOrEmpty(text)) return null;
return Convert.ToDateTime(text);
}
public override string ConvertToString(TypeConverterOptions options, object value)
{
if (value == null) return "";
var dateTime = (DateTime) value;
return dateTime.ToString(DateStringFormat);
}
}
the model looks like this
public class MyModel
{
// ..... removed other properties
public DateTime MyDateTime {get; set};
}
And the CsvClassMapper looks like this
public sealed class CsvMap : CsvClassMap<MyModel>
{
public CsvMap()
{
Map(m => m.MyDateTime).TypeConverter<CsvDateTimeConverter>();
}
}
I registered the class map to the reader and writer. But the problem is:
I can successfully write to the CSV file using the converter but when I try to read from CSV file, it can read the other properties but returns null for the DateTime property.
So.. what am I missing?
EDITED:
if it helps:
this is the code to read from csv file:
using (TextReader reader = new StreamReader(filePath))
{
var csv = new CsvReader(reader);
csv.Configuration.RegisterClassMap<CsvMap>();
csv.Configuration.Encoding = Encoding.UTF8;
return csv.GetRecords<MyModel>().ToList();
}
You don't need to specify a custom type converter. This is the US-style DateTime format, with milliseconds added. All you need is to specify the correct culture and possibly, the format to be used.
One option is to set the Configuration.CultureInfo
property to the US culture :
reader.Configuration.CultureInfo = CultureInfo.GetCultureInfo("en-US");
The following code will generate and read US dates without milliseconds :
using (var file = new StreamWriter("test.csv"))
{
var writer = new CsvWriter(file);
writer.Configuration.CultureInfo = CultureInfo.GetCultureInfo("en-US");
writer.WriteRecords(items);
}
using (var file = new StreamReader("test.csv"))
{
var reader= new CsvReader(file);
reader.Configuration.CultureInfo = CultureInfo.GetCultureInfo("en-US");
var models=reader.GetRecords<MyModel>().ToArray();
Console.WriteLine(models[0]);
}
You can specify a different format for the field through the TypeConverterOptions
method in the class map :
public sealed class CsvMap : CsvHelper.Configuration.CsvClassMap<MyModel>
{
public CsvMap()
{
Map(m => m.Date).TypeConverterOption("MM/dd/yyyy HH:mm:ss.fff");
Map(m => m.ID);
}
}
The following code, which just adds the class map, will generate US dates with milliseconds :
using (var file = new StreamWriter("test.csv"))
{
var writer = new CsvWriter(file);
writer.Configuration.CultureInfo = CultureInfo.GetCultureInfo("en-US");
writer.Configuration.RegisterClassMap<CsvMap>();
writer.WriteRecords(items);
}
using (var file = new StreamReader("test.csv"))
{
var reader= new CsvReader(file);
reader.Configuration.CultureInfo = CultureInfo.GetCultureInfo("en-US");
reader.Configuration.RegisterClassMap<CsvMap>();
var models=reader.GetRecords<MyModel>().ToArray();
Console.WriteLine(models[0]);
}
Date,ID
10/12/2017 11:56:11.016,1
10/11/2017 11:56:11.021,2
You can specify the CultureInfo as a TypeConverterOption as well, if you don't want to set the culture for the entire file :
public CsvMap()
{
Map(m => m.Date).TypeConverterOption("MM/dd/yyyy HH:mm:ss.fff")
.TypeConverterOption(CultureInfo.GetCultureInfo("en-US"));
Map(m => m.ID);
}
NOTE
CsvHelper released a major update (3.0) recently (as in this week). It wasn't there last week! The current version is 3.2.0.
In this version, CsvClassMap
becomes CsvMap
and the TypeConverterOptions method becomes an object with methods that return a MapMember :
public CsvMap()
{
string format="MM/dd/yyyy HH:mm:ss.fff";
var enUS=CultureInfo.GetCultureInfo("en-US");
Map(m => m.Date).TypeConverterOption.Format(format)
.TypeConverterOption.CultureInfo(enUS);
Map(m => m.ID);
}
There's still no documentation for Type Converters much less TypeConverterOptions. I found the method from this Github issue which should serve as documentation.
The other option is to check the source code itself.