diff --git a/src/ServiceStack.Text/CsvConfig.cs b/src/ServiceStack.Text/CsvConfig.cs index c6e73b23c..03fb3fe51 100644 --- a/src/ServiceStack.Text/CsvConfig.cs +++ b/src/ServiceStack.Text/CsvConfig.cs @@ -110,7 +110,23 @@ public static string RowSeparatorString ResetEscapeStrings(); } } - + + [ThreadStatic] + private static string tsDateTimeFormatString; + private static string sDateTimeFormatString; + public static string DateTimeFormatString + { + get + { + return tsDateTimeFormatString ?? sDateTimeFormatString ?? ""; + } + set + { + tsDateTimeFormatString = value; + if (sDateTimeFormatString == null) sDateTimeFormatString = value; + } + } + public static void Reset() { tsItemSeperatorString = sItemSeperatorString = null; @@ -118,6 +134,7 @@ public static void Reset() tsEscapedItemDelimiterString = sEscapedItemDelimiterString = null; tsRowSeparatorString = sRowSeparatorString = null; tsEscapeStrings = sEscapeStrings = null; + tsDateTimeFormatString = sDateTimeFormatString = null; } } diff --git a/src/ServiceStack.Text/TextExtensions.cs b/src/ServiceStack.Text/TextExtensions.cs index 2b0d9ff13..ecf782c6a 100644 --- a/src/ServiceStack.Text/TextExtensions.cs +++ b/src/ServiceStack.Text/TextExtensions.cs @@ -31,6 +31,23 @@ public static string ToCsvField(this string text) ); } + public static string ToCsvField(this DateTime value) + { + return !JsWriter.HasAnyEscapeChars(value.ToString()) + ? value.ToString(CsvConfig.DateTimeFormatString) + : string.Concat + ( + JsWriter.QuoteString, + value.ToString(CsvConfig.DateTimeFormatString).Replace(JsWriter.QuoteString, TypeSerializer.DoubleQuoteString), + JsWriter.QuoteString + ); + } + + public static string ToCsvField(this DateTime? value) + { + return value.HasValue ? value.Value.ToCsvField() : null; + } + public static object ToCsvField(this object text) { return text == null || !JsWriter.HasAnyEscapeChars(text.ToString()) diff --git a/tests/ServiceStack.Text.Tests/CsvStreamTests.cs b/tests/ServiceStack.Text.Tests/CsvStreamTests.cs index 394463bad..408c3b9e5 100644 --- a/tests/ServiceStack.Text.Tests/CsvStreamTests.cs +++ b/tests/ServiceStack.Text.Tests/CsvStreamTests.cs @@ -179,6 +179,39 @@ public void Can_convert_to_csv_field() Assert.That("7,7.1".ToCsvField(), Is.EqualTo("\"7,7.1\"")); Assert.That("\"7,7.1\"".ToCsvField(), Is.EqualTo("\"\"\"7,7.1\"\"\"")); } + + [Test] + public void Can_convert_to_csv_field_from_datetime_respecting_configured_datetimeformat() + { + var dateTime = DateTime.Now; + var testCases = new[] { "yyyy-MM-ddTHH:mm:ss.fff zzzz", "MM/dd/yyyy", "dd/MM/yyyy", "HH:mm:ss", "arbitrary string" }; + foreach (var format in testCases) + { + CsvConfig.DateTimeFormatString = format; + Assert.That(dateTime.ToCsvField(),Is.EqualTo("\"" + dateTime.ToString(format) + "\"")); + } + } + + [Test] + public void Can_convert_to_csv_field_from_nullabledatetime_respecting_configured_datetimeformat() + { + DateTime? dateTime = DateTime.Now; + var testCases = new[] { "yyyy-MM-ddTHH:mm:ss.fff zzzz", "MM/dd/yyyy", "dd/MM/yyyy", "HH:mm:ss", "arbitrary string" }; + foreach (var format in testCases) + { + CsvConfig.DateTimeFormatString = format; + Assert.That(dateTime.ToCsvField(), Is.EqualTo("\"" + dateTime.Value.ToString(format) + "\"")); + } + DateTime? nullDateTime = null; + Assert.That(nullDateTime.ToCsvField(), Is.EqualTo(null)); + } + + [Test] + public void Can_convert_to_csv_field_from_datetime_without_a_configured_datetimeformat() + { + CsvConfig.Reset(); + Assert.That(DateTime.Now.ToCsvField(), Is.EqualTo("\"" + DateTime.Now + "\"")); + } [Test] public void Can_convert_to_csv_field_pipe_separator()