Skip to content

Commit cf41ec3

Browse files
committed
Allow storing TimeSpans as strings (default to ticks)
1 parent 78cd2cd commit cf41ec3

File tree

4 files changed

+131
-12
lines changed

4 files changed

+131
-12
lines changed

src/SQLite.cs

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,11 @@ public partial class SQLiteConnection : IDisposable
210210
/// </summary>
211211
public bool StoreDateTimeAsTicks { get; private set; }
212212

213+
/// <summary>
214+
/// Whether to store TimeSpan properties as ticks (true) or strings (false).
215+
/// </summary>
216+
public bool StoreTimeSpanAsTicks { get; private set; }
217+
213218
/// <summary>
214219
/// The format to use when storing DateTime properties as strings. Ignored if StoreDateTimeAsTicks is true.
215220
/// </summary>
@@ -560,7 +565,7 @@ public CreateTableResult CreateTable (Type ty, CreateFlags createFlags = CreateF
560565

561566
// Build query.
562567
var query = "create " + @virtual + "table if not exists \"" + map.TableName + "\" " + @using + "(\n";
563-
var decls = map.Columns.Select (p => Orm.SqlDecl (p, StoreDateTimeAsTicks));
568+
var decls = map.Columns.Select (p => Orm.SqlDecl (p, StoreDateTimeAsTicks, StoreTimeSpanAsTicks));
564569
var decl = string.Join (",\n", decls.ToArray ());
565570
query += decl;
566571
query += ")";
@@ -826,7 +831,7 @@ void MigrateTable (TableMapping map, List<ColumnInfo> existingCols)
826831
}
827832

828833
foreach (var p in toBeAdded) {
829-
var addCol = "alter table \"" + map.TableName + "\" add column " + Orm.SqlDecl (p, StoreDateTimeAsTicks);
834+
var addCol = "alter table \"" + map.TableName + "\" add column " + Orm.SqlDecl (p, StoreDateTimeAsTicks, StoreTimeSpanAsTicks);
830835
Execute (addCol);
831836
}
832837
}
@@ -2110,6 +2115,7 @@ public class SQLiteConnectionString
21102115
public string UniqueKey { get; }
21112116
public string DatabasePath { get; }
21122117
public bool StoreDateTimeAsTicks { get; }
2118+
public bool StoreTimeSpanAsTicks { get; }
21132119
public string DateTimeStringFormat { get; }
21142120
public System.Globalization.DateTimeStyles DateTimeStyle { get; }
21152121
public object Key { get; }
@@ -2219,13 +2225,20 @@ public SQLiteConnectionString (string databasePath, bool storeDateTimeAsTicks, o
22192225
/// <param name="dateTimeStringFormat">
22202226
/// Specifies the format to use when storing DateTime properties as strings.
22212227
/// </param>
2222-
public SQLiteConnectionString (string databasePath, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks, object key = null, Action<SQLiteConnection> preKeyAction = null, Action<SQLiteConnection> postKeyAction = null, string vfsName = null, string dateTimeStringFormat = DateTimeSqliteDefaultFormat)
2228+
/// <param name="storeTimeSpanAsTicks">
2229+
/// Specifies whether to store TimeSpan properties as ticks (true) or strings (false). You
2230+
/// absolutely do want to store them as Ticks in all new projects. The value of false is
2231+
/// only here for backwards compatibility. There is a *significant* speed advantage, with no
2232+
/// down sides, when setting storeTimeSpanAsTicks = true.
2233+
/// </param>
2234+
public SQLiteConnectionString (string databasePath, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks, object key = null, Action<SQLiteConnection> preKeyAction = null, Action<SQLiteConnection> postKeyAction = null, string vfsName = null, string dateTimeStringFormat = DateTimeSqliteDefaultFormat, bool storeTimeSpanAsTicks = true)
22232235
{
22242236
if (key != null && !((key is byte[]) || (key is string)))
22252237
throw new ArgumentException ("Encryption keys must be strings or byte arrays", nameof (key));
22262238

22272239
UniqueKey = string.Format ("{0}_{1:X8}", databasePath, (uint)openFlags);
22282240
StoreDateTimeAsTicks = storeDateTimeAsTicks;
2241+
StoreTimeSpanAsTicks = storeTimeSpanAsTicks;
22292242
DateTimeStringFormat = dateTimeStringFormat;
22302243
DateTimeStyle = "o".Equals (DateTimeStringFormat, StringComparison.OrdinalIgnoreCase) || "r".Equals (DateTimeStringFormat, StringComparison.OrdinalIgnoreCase) ? System.Globalization.DateTimeStyles.RoundtripKind : System.Globalization.DateTimeStyles.None;
22312244
Key = key;
@@ -2623,9 +2636,9 @@ public static Type GetType (object obj)
26232636
return obj.GetType ();
26242637
}
26252638

2626-
public static string SqlDecl (TableMapping.Column p, bool storeDateTimeAsTicks)
2639+
public static string SqlDecl (TableMapping.Column p, bool storeDateTimeAsTicks, bool storeTimeSpanAsTicks)
26272640
{
2628-
string decl = "\"" + p.Name + "\" " + SqlType (p, storeDateTimeAsTicks) + " ";
2641+
string decl = "\"" + p.Name + "\" " + SqlType (p, storeDateTimeAsTicks, storeTimeSpanAsTicks) + " ";
26292642

26302643
if (p.IsPK) {
26312644
decl += "primary key ";
@@ -2643,7 +2656,7 @@ public static string SqlDecl (TableMapping.Column p, bool storeDateTimeAsTicks)
26432656
return decl;
26442657
}
26452658

2646-
public static string SqlType (TableMapping.Column p, bool storeDateTimeAsTicks)
2659+
public static string SqlType (TableMapping.Column p, bool storeDateTimeAsTicks, bool storeTimeSpanAsTicks)
26472660
{
26482661
var clrType = p.ColumnType;
26492662
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)) {
@@ -2661,7 +2674,7 @@ public static string SqlType (TableMapping.Column p, bool storeDateTimeAsTicks)
26612674
return "varchar";
26622675
}
26632676
else if (clrType == typeof (TimeSpan)) {
2664-
return "bigint";
2677+
return storeTimeSpanAsTicks ? "bigint" : "time";
26652678
}
26662679
else if (clrType == typeof (DateTime)) {
26672680
return storeDateTimeAsTicks ? "bigint" : "datetime";
@@ -2947,13 +2960,13 @@ void BindAll (Sqlite3Statement stmt)
29472960
b.Index = nextIdx++;
29482961
}
29492962

2950-
BindParameter (stmt, b.Index, b.Value, _conn.StoreDateTimeAsTicks, _conn.DateTimeStringFormat);
2963+
BindParameter (stmt, b.Index, b.Value, _conn.StoreDateTimeAsTicks, _conn.DateTimeStringFormat, _conn.StoreTimeSpanAsTicks);
29512964
}
29522965
}
29532966

29542967
static IntPtr NegativePointer = new IntPtr (-1);
29552968

2956-
internal static void BindParameter (Sqlite3Statement stmt, int index, object value, bool storeDateTimeAsTicks, string dateTimeStringFormat)
2969+
internal static void BindParameter (Sqlite3Statement stmt, int index, object value, bool storeDateTimeAsTicks, string dateTimeStringFormat, bool storeTimeSpanAsTicks)
29572970
{
29582971
if (value == null) {
29592972
SQLite3.BindNull (stmt, index);
@@ -2978,7 +2991,12 @@ internal static void BindParameter (Sqlite3Statement stmt, int index, object val
29782991
SQLite3.BindDouble (stmt, index, Convert.ToDouble (value));
29792992
}
29802993
else if (value is TimeSpan) {
2981-
SQLite3.BindInt64 (stmt, index, ((TimeSpan)value).Ticks);
2994+
if (storeTimeSpanAsTicks) {
2995+
SQLite3.BindInt64 (stmt, index, ((TimeSpan)value).Ticks);
2996+
}
2997+
else {
2998+
SQLite3.BindText (stmt, index, ((TimeSpan)value).ToString (), -1, NegativePointer);
2999+
}
29823000
}
29833001
else if (value is DateTime) {
29843002
if (storeDateTimeAsTicks) {
@@ -3061,7 +3079,17 @@ object ReadCol (Sqlite3Statement stmt, int index, SQLite3.ColType type, Type clr
30613079
return (float)SQLite3.ColumnDouble (stmt, index);
30623080
}
30633081
else if (clrType == typeof (TimeSpan)) {
3064-
return new TimeSpan (SQLite3.ColumnInt64 (stmt, index));
3082+
if (_conn.StoreTimeSpanAsTicks) {
3083+
return new TimeSpan (SQLite3.ColumnInt64 (stmt, index));
3084+
}
3085+
else {
3086+
var text = SQLite3.ColumnString (stmt, index);
3087+
TimeSpan resultTime;
3088+
if (!TimeSpan.TryParseExact (text, "c", System.Globalization.CultureInfo.InvariantCulture, System.Globalization.TimeSpanStyles.None, out resultTime)) {
3089+
resultTime = TimeSpan.Parse (text);
3090+
}
3091+
return resultTime;
3092+
}
30653093
}
30663094
else if (clrType == typeof (DateTime)) {
30673095
if (_conn.StoreDateTimeAsTicks) {
@@ -3174,7 +3202,7 @@ public int ExecuteNonQuery (object[] source)
31743202
//bind the values.
31753203
if (source != null) {
31763204
for (int i = 0; i < source.Length; i++) {
3177-
SQLiteCommand.BindParameter (Statement, i + 1, source[i], Connection.StoreDateTimeAsTicks, Connection.DateTimeStringFormat);
3205+
SQLiteCommand.BindParameter (Statement, i + 1, source[i], Connection.StoreDateTimeAsTicks, Connection.DateTimeStringFormat, Connection.StoreTimeSpanAsTicks);
31783206
}
31793207
}
31803208
r = SQLite3.Step (Statement);

src/SQLiteAsync.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,11 @@ public Task EnableWriteAheadLoggingAsync ()
137137
/// Whether to store DateTime properties as ticks (true) or strings (false).
138138
/// </summary>
139139
public bool StoreDateTimeAsTicks => GetConnection ().StoreDateTimeAsTicks;
140+
141+
/// <summary>
142+
/// Whether to store TimeSpan properties as ticks (true) or strings (false).
143+
/// </summary>
144+
public bool StoreTimeSpanAsTicks => GetConnection ().StoreTimeSpanAsTicks;
140145

141146
/// <summary>
142147
/// Whether to writer queries to <see cref="Tracer"/> during execution.

tests/SQLite.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
<Compile Include="BackupTest.cs" />
8585
<Compile Include="ReadmeTest.cs" />
8686
<Compile Include="QueryTest.cs" />
87+
<Compile Include="TimeSpanTest.cs" />
8788
</ItemGroup>
8889
<ItemGroup>
8990
<None Include="packages.config" />

tests/TimeSpanTest.cs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
4+
#if NETFX_CORE
5+
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
6+
using SetUp = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestInitializeAttribute;
7+
using TestFixture = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestClassAttribute;
8+
using Test = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestMethodAttribute;
9+
#else
10+
using NUnit.Framework;
11+
#endif
12+
13+
namespace SQLite.Tests
14+
{
15+
[TestFixture]
16+
public class TimeSpanTest
17+
{
18+
class TestObj
19+
{
20+
[PrimaryKey, AutoIncrement]
21+
public int Id { get; set; }
22+
23+
public string Name { get; set; }
24+
public TimeSpan Duration { get; set; }
25+
}
26+
27+
[Test]
28+
public void AsTicks ()
29+
{
30+
var db = new TestDb (TimeSpanAsTicks (true));
31+
TestTimeSpan (db);
32+
}
33+
34+
[Test]
35+
public void AsStrings ()
36+
{
37+
var db = new TestDb (TimeSpanAsTicks (false));
38+
TestTimeSpan (db);
39+
}
40+
41+
[Test]
42+
public void AsyncAsTicks ()
43+
{
44+
var db = new SQLiteAsyncConnection (TimeSpanAsTicks (true));
45+
TestAsyncTimeSpan (db);
46+
}
47+
48+
[Test]
49+
public void AsyncAsStrings ()
50+
{
51+
var db = new SQLiteAsyncConnection (TimeSpanAsTicks (false));
52+
TestAsyncTimeSpan (db);
53+
}
54+
55+
SQLiteConnectionString TimeSpanAsTicks (bool asTicks = true) => new SQLiteConnectionString (TestPath.GetTempFileName (), SQLiteOpenFlags.Create | SQLiteOpenFlags.ReadWrite, true, storeTimeSpanAsTicks: asTicks);
56+
57+
void TestAsyncTimeSpan (SQLiteAsyncConnection db)
58+
{
59+
db.CreateTableAsync<TestObj> ().Wait ();
60+
61+
TestObj o, o2;
62+
63+
o = new TestObj {
64+
Duration = new TimeSpan (42, 12, 33, 20, 501),
65+
};
66+
db.InsertAsync (o).Wait ();
67+
o2 = db.GetAsync<TestObj> (o.Id).Result;
68+
Assert.AreEqual (o.Duration, o2.Duration);
69+
}
70+
71+
void TestTimeSpan (TestDb db)
72+
{
73+
db.CreateTable<TestObj> ();
74+
75+
TestObj o, o2;
76+
77+
o = new TestObj {
78+
Duration = new TimeSpan (42, 12, 33, 20, 501),
79+
};
80+
db.Insert (o);
81+
o2 = db.Get<TestObj> (o.Id);
82+
Assert.AreEqual (o.Duration, o2.Duration);
83+
}
84+
}
85+
}

0 commit comments

Comments
 (0)