diff --git a/src/SQLite.cs b/src/SQLite.cs
index c1eac952..7504a8d3 100644
--- a/src/SQLite.cs
+++ b/src/SQLite.cs
@@ -210,6 +210,23 @@ public partial class SQLiteConnection : IDisposable
///
public bool StoreDateTimeAsTicks { get; private set; }
+ ///
+ /// Whether to store TimeSpan properties as ticks (true) or strings (false).
+ ///
+ public bool StoreTimeSpanAsTicks { get; private set; }
+
+ ///
+ /// The format to use when storing DateTime properties as strings. Ignored if StoreDateTimeAsTicks is true.
+ ///
+ /// The date time string format.
+ public string DateTimeStringFormat { get; private set; }
+
+ ///
+ /// The DateTimeStyles value to use when parsing a DateTime property string.
+ ///
+ /// The date time style.
+ internal System.Globalization.DateTimeStyles DateTimeStyle { get; private set; }
+
#if USE_SQLITEPCL_RAW && !NO_SQLITEPCL_RAW_BATTERIES
static SQLiteConnection ()
{
@@ -298,6 +315,8 @@ public SQLiteConnection (SQLiteConnectionString connectionString)
_open = true;
StoreDateTimeAsTicks = connectionString.StoreDateTimeAsTicks;
+ DateTimeStringFormat = connectionString.DateTimeStringFormat;
+ DateTimeStyle = connectionString.DateTimeStyle;
BusyTimeout = TimeSpan.FromSeconds (0.1);
Tracer = line => Debug.WriteLine (line);
@@ -546,7 +565,7 @@ public CreateTableResult CreateTable (Type ty, CreateFlags createFlags = CreateF
// Build query.
var query = "create " + @virtual + "table if not exists \"" + map.TableName + "\" " + @using + "(\n";
- var decls = map.Columns.Select (p => Orm.SqlDecl (p, StoreDateTimeAsTicks));
+ var decls = map.Columns.Select (p => Orm.SqlDecl (p, StoreDateTimeAsTicks, StoreTimeSpanAsTicks));
var decl = string.Join (",\n", decls.ToArray ());
query += decl;
query += ")";
@@ -812,7 +831,7 @@ void MigrateTable (TableMapping map, List existingCols)
}
foreach (var p in toBeAdded) {
- var addCol = "alter table \"" + map.TableName + "\" add column " + Orm.SqlDecl (p, StoreDateTimeAsTicks);
+ var addCol = "alter table \"" + map.TableName + "\" add column " + Orm.SqlDecl (p, StoreDateTimeAsTicks, StoreTimeSpanAsTicks);
Execute (addCol);
}
}
@@ -2090,9 +2109,14 @@ public enum NotifyTableChangedAction
///
public class SQLiteConnectionString
{
+ const string DateTimeSqliteDefaultFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff";
+
public string UniqueKey { get; }
public string DatabasePath { get; }
public bool StoreDateTimeAsTicks { get; }
+ public bool StoreTimeSpanAsTicks { get; }
+ public string DateTimeStringFormat { get; }
+ public System.Globalization.DateTimeStyles DateTimeStyle { get; }
public object Key { get; }
public SQLiteOpenFlags OpenFlags { get; }
public Action PreKeyAction { get; }
@@ -2194,13 +2218,25 @@ public SQLiteConnectionString (string databasePath, bool storeDateTimeAsTicks, o
///
/// Specifies the Virtual File System to use on the database.
///
- public SQLiteConnectionString (string databasePath, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks, object key = null, Action preKeyAction = null, Action postKeyAction = null, string vfsName = null)
+ ///
+ /// Specifies the format to use when storing DateTime properties as strings.
+ ///
+ ///
+ /// Specifies whether to store TimeSpan properties as ticks (true) or strings (false). You
+ /// absolutely do want to store them as Ticks in all new projects. The value of false is
+ /// only here for backwards compatibility. There is a *significant* speed advantage, with no
+ /// down sides, when setting storeTimeSpanAsTicks = true.
+ ///
+ public SQLiteConnectionString (string databasePath, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks, object key = null, Action preKeyAction = null, Action postKeyAction = null, string vfsName = null, string dateTimeStringFormat = DateTimeSqliteDefaultFormat, bool storeTimeSpanAsTicks = true)
{
if (key != null && !((key is byte[]) || (key is string)))
throw new ArgumentException ("Encryption keys must be strings or byte arrays", nameof (key));
UniqueKey = string.Format ("{0}_{1:X8}", databasePath, (uint)openFlags);
StoreDateTimeAsTicks = storeDateTimeAsTicks;
+ StoreTimeSpanAsTicks = storeTimeSpanAsTicks;
+ DateTimeStringFormat = dateTimeStringFormat;
+ DateTimeStyle = "o".Equals (DateTimeStringFormat, StringComparison.OrdinalIgnoreCase) || "r".Equals (DateTimeStringFormat, StringComparison.OrdinalIgnoreCase) ? System.Globalization.DateTimeStyles.RoundtripKind : System.Globalization.DateTimeStyles.None;
Key = key;
PreKeyAction = preKeyAction;
PostKeyAction = postKeyAction;
@@ -2596,9 +2632,9 @@ public static Type GetType (object obj)
return obj.GetType ();
}
- public static string SqlDecl (TableMapping.Column p, bool storeDateTimeAsTicks)
+ public static string SqlDecl (TableMapping.Column p, bool storeDateTimeAsTicks, bool storeTimeSpanAsTicks)
{
- string decl = "\"" + p.Name + "\" " + SqlType (p, storeDateTimeAsTicks) + " ";
+ string decl = "\"" + p.Name + "\" " + SqlType (p, storeDateTimeAsTicks, storeTimeSpanAsTicks) + " ";
if (p.IsPK) {
decl += "primary key ";
@@ -2616,7 +2652,7 @@ public static string SqlDecl (TableMapping.Column p, bool storeDateTimeAsTicks)
return decl;
}
- public static string SqlType (TableMapping.Column p, bool storeDateTimeAsTicks)
+ public static string SqlType (TableMapping.Column p, bool storeDateTimeAsTicks, bool storeTimeSpanAsTicks)
{
var clrType = p.ColumnType;
if (clrType == typeof (Boolean) || clrType == typeof (Byte) || clrType == typeof (UInt16) || clrType == typeof (SByte) || clrType == typeof (Int16) || clrType == typeof (Int32) || clrType == typeof (UInt32) || clrType == typeof (Int64)) {
@@ -2634,7 +2670,7 @@ public static string SqlType (TableMapping.Column p, bool storeDateTimeAsTicks)
return "varchar";
}
else if (clrType == typeof (TimeSpan)) {
- return "bigint";
+ return storeTimeSpanAsTicks ? "bigint" : "time";
}
else if (clrType == typeof (DateTime)) {
return storeDateTimeAsTicks ? "bigint" : "datetime";
@@ -2920,15 +2956,13 @@ void BindAll (Sqlite3Statement stmt)
b.Index = nextIdx++;
}
- BindParameter (stmt, b.Index, b.Value, _conn.StoreDateTimeAsTicks);
+ BindParameter (stmt, b.Index, b.Value, _conn.StoreDateTimeAsTicks, _conn.DateTimeStringFormat, _conn.StoreTimeSpanAsTicks);
}
}
static IntPtr NegativePointer = new IntPtr (-1);
- const string DateTimeExactStoreFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff";
-
- internal static void BindParameter (Sqlite3Statement stmt, int index, object value, bool storeDateTimeAsTicks)
+ internal static void BindParameter (Sqlite3Statement stmt, int index, object value, bool storeDateTimeAsTicks, string dateTimeStringFormat, bool storeTimeSpanAsTicks)
{
if (value == null) {
SQLite3.BindNull (stmt, index);
@@ -2953,14 +2987,19 @@ internal static void BindParameter (Sqlite3Statement stmt, int index, object val
SQLite3.BindDouble (stmt, index, Convert.ToDouble (value));
}
else if (value is TimeSpan) {
- SQLite3.BindInt64 (stmt, index, ((TimeSpan)value).Ticks);
+ if (storeTimeSpanAsTicks) {
+ SQLite3.BindInt64 (stmt, index, ((TimeSpan)value).Ticks);
+ }
+ else {
+ SQLite3.BindText (stmt, index, ((TimeSpan)value).ToString (), -1, NegativePointer);
+ }
}
else if (value is DateTime) {
if (storeDateTimeAsTicks) {
SQLite3.BindInt64 (stmt, index, ((DateTime)value).Ticks);
}
else {
- SQLite3.BindText (stmt, index, ((DateTime)value).ToString (DateTimeExactStoreFormat, System.Globalization.CultureInfo.InvariantCulture), -1, NegativePointer);
+ SQLite3.BindText (stmt, index, ((DateTime)value).ToString (dateTimeStringFormat, System.Globalization.CultureInfo.InvariantCulture), -1, NegativePointer);
}
}
else if (value is DateTimeOffset) {
@@ -3036,7 +3075,17 @@ object ReadCol (Sqlite3Statement stmt, int index, SQLite3.ColType type, Type clr
return (float)SQLite3.ColumnDouble (stmt, index);
}
else if (clrType == typeof (TimeSpan)) {
- return new TimeSpan (SQLite3.ColumnInt64 (stmt, index));
+ if (_conn.StoreTimeSpanAsTicks) {
+ return new TimeSpan (SQLite3.ColumnInt64 (stmt, index));
+ }
+ else {
+ var text = SQLite3.ColumnString (stmt, index);
+ TimeSpan resultTime;
+ if (!TimeSpan.TryParseExact (text, "c", System.Globalization.CultureInfo.InvariantCulture, System.Globalization.TimeSpanStyles.None, out resultTime)) {
+ resultTime = TimeSpan.Parse (text);
+ }
+ return resultTime;
+ }
}
else if (clrType == typeof (DateTime)) {
if (_conn.StoreDateTimeAsTicks) {
@@ -3045,7 +3094,7 @@ object ReadCol (Sqlite3Statement stmt, int index, SQLite3.ColType type, Type clr
else {
var text = SQLite3.ColumnString (stmt, index);
DateTime resultDate;
- if (!DateTime.TryParseExact (text, DateTimeExactStoreFormat, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out resultDate)) {
+ if (!DateTime.TryParseExact (text, _conn.DateTimeStringFormat, System.Globalization.CultureInfo.InvariantCulture, _conn.DateTimeStyle, out resultDate)) {
resultDate = DateTime.Parse (text);
}
return resultDate;
@@ -3149,7 +3198,7 @@ public int ExecuteNonQuery (object[] source)
//bind the values.
if (source != null) {
for (int i = 0; i < source.Length; i++) {
- SQLiteCommand.BindParameter (Statement, i + 1, source[i], Connection.StoreDateTimeAsTicks);
+ SQLiteCommand.BindParameter (Statement, i + 1, source[i], Connection.StoreDateTimeAsTicks, Connection.DateTimeStringFormat, Connection.StoreTimeSpanAsTicks);
}
}
r = SQLite3.Step (Statement);
diff --git a/src/SQLiteAsync.cs b/src/SQLiteAsync.cs
index 77255098..ac82d828 100644
--- a/src/SQLiteAsync.cs
+++ b/src/SQLiteAsync.cs
@@ -137,6 +137,11 @@ public Task EnableWriteAheadLoggingAsync ()
/// Whether to store DateTime properties as ticks (true) or strings (false).
///
public bool StoreDateTimeAsTicks => GetConnection ().StoreDateTimeAsTicks;
+
+ ///
+ /// Whether to store TimeSpan properties as ticks (true) or strings (false).
+ ///
+ public bool StoreTimeSpanAsTicks => GetConnection ().StoreTimeSpanAsTicks;
///
/// Whether to writer queries to during execution.
diff --git a/tests/DateTimeTest.cs b/tests/DateTimeTest.cs
index 37e42fbe..949c8051 100644
--- a/tests/DateTimeTest.cs
+++ b/tests/DateTimeTest.cs
@@ -35,7 +35,15 @@ public void AsTicks ()
[Test]
public void AsStrings ()
{
- var db = new TestDb (storeDateTimeAsTicks: false);
+ var db = new TestDb (storeDateTimeAsTicks: false);
+ TestDateTime (db);
+ }
+
+ [TestCase ("o")]
+ [TestCase ("MMM'-'dd'-'yyyy' 'HH':'mm':'ss'.'fffffff")]
+ public void AsCustomStrings (string format)
+ {
+ var db = new TestDb (CustomDateTimeString (format));
TestDateTime (db);
}
@@ -53,6 +61,16 @@ public void AsyncAsString ()
TestAsyncDateTime (db);
}
+ [TestCase ("o")]
+ [TestCase ("MMM'-'dd'-'yyyy' 'HH':'mm':'ss'.'fffffff")]
+ public void AsyncAsCustomStrings (string format)
+ {
+ var db = new SQLiteAsyncConnection (CustomDateTimeString (format));
+ TestAsyncDateTime (db);
+ }
+
+ SQLiteConnectionString CustomDateTimeString (string dateTimeFormat) => new SQLiteConnectionString (TestPath.GetTempFileName (), false, dateTimeFormat);
+
void TestAsyncDateTime (SQLiteAsyncConnection db)
{
db.CreateTableAsync ().Wait ();
diff --git a/tests/SQLite.Tests.csproj b/tests/SQLite.Tests.csproj
index 77d8dc7a..06e4de58 100644
--- a/tests/SQLite.Tests.csproj
+++ b/tests/SQLite.Tests.csproj
@@ -84,6 +84,7 @@
+
diff --git a/tests/TestDb.cs b/tests/TestDb.cs
index 9b4ed8d9..56cd3a99 100644
--- a/tests/TestDb.cs
+++ b/tests/TestDb.cs
@@ -60,6 +60,13 @@ public TestDb (bool storeDateTimeAsTicks = true, object key = null, bool wal = t
EnableWriteAheadLogging ();
}
+ public TestDb (SQLiteConnectionString connectionString, bool wal = true) : base (connectionString)
+ {
+ Trace = true;
+ if (wal)
+ EnableWriteAheadLogging ();
+ }
+
public TestDb (string path, bool storeDateTimeAsTicks = true, object key = null, bool wal = true) : base (new SQLiteConnectionString (path, storeDateTimeAsTicks, key: key))
{
Trace = true;
diff --git a/tests/TimeSpanTest.cs b/tests/TimeSpanTest.cs
new file mode 100644
index 00000000..af169a17
--- /dev/null
+++ b/tests/TimeSpanTest.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Threading.Tasks;
+
+#if NETFX_CORE
+using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
+using SetUp = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestInitializeAttribute;
+using TestFixture = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestClassAttribute;
+using Test = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestMethodAttribute;
+#else
+using NUnit.Framework;
+#endif
+
+namespace SQLite.Tests
+{
+ [TestFixture]
+ public class TimeSpanTest
+ {
+ class TestObj
+ {
+ [PrimaryKey, AutoIncrement]
+ public int Id { get; set; }
+
+ public string Name { get; set; }
+ public TimeSpan Duration { get; set; }
+ }
+
+ [Test]
+ public void AsTicks ()
+ {
+ var db = new TestDb (TimeSpanAsTicks (true));
+ TestTimeSpan (db);
+ }
+
+ [Test]
+ public void AsStrings ()
+ {
+ var db = new TestDb (TimeSpanAsTicks (false));
+ TestTimeSpan (db);
+ }
+
+ [Test]
+ public void AsyncAsTicks ()
+ {
+ var db = new SQLiteAsyncConnection (TimeSpanAsTicks (true));
+ TestAsyncTimeSpan (db);
+ }
+
+ [Test]
+ public void AsyncAsStrings ()
+ {
+ var db = new SQLiteAsyncConnection (TimeSpanAsTicks (false));
+ TestAsyncTimeSpan (db);
+ }
+
+ SQLiteConnectionString TimeSpanAsTicks (bool asTicks = true) => new SQLiteConnectionString (TestPath.GetTempFileName (), SQLiteOpenFlags.Create | SQLiteOpenFlags.ReadWrite, true, storeTimeSpanAsTicks: asTicks);
+
+ void TestAsyncTimeSpan (SQLiteAsyncConnection db)
+ {
+ db.CreateTableAsync ().Wait ();
+
+ TestObj o, o2;
+
+ o = new TestObj {
+ Duration = new TimeSpan (42, 12, 33, 20, 501),
+ };
+ db.InsertAsync (o).Wait ();
+ o2 = db.GetAsync (o.Id).Result;
+ Assert.AreEqual (o.Duration, o2.Duration);
+ }
+
+ void TestTimeSpan (TestDb db)
+ {
+ db.CreateTable ();
+
+ TestObj o, o2;
+
+ o = new TestObj {
+ Duration = new TimeSpan (42, 12, 33, 20, 501),
+ };
+ db.Insert (o);
+ o2 = db.Get (o.Id);
+ Assert.AreEqual (o.Duration, o2.Duration);
+ }
+ }
+}