Skip to content

Commit 9f7c08c

Browse files
authored
Merge pull request #869 from perfectco/custom-datetime
Custom datetime format, string-based timespan
2 parents 1454c82 + 8649a68 commit 9f7c08c

File tree

6 files changed

+182
-17
lines changed

6 files changed

+182
-17
lines changed

src/SQLite.cs

Lines changed: 65 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,23 @@ 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+
218+
/// <summary>
219+
/// The format to use when storing DateTime properties as strings. Ignored if StoreDateTimeAsTicks is true.
220+
/// </summary>
221+
/// <value>The date time string format.</value>
222+
public string DateTimeStringFormat { get; private set; }
223+
224+
/// <summary>
225+
/// The DateTimeStyles value to use when parsing a DateTime property string.
226+
/// </summary>
227+
/// <value>The date time style.</value>
228+
internal System.Globalization.DateTimeStyles DateTimeStyle { get; private set; }
229+
213230
#if USE_SQLITEPCL_RAW && !NO_SQLITEPCL_RAW_BATTERIES
214231
static SQLiteConnection ()
215232
{
@@ -298,6 +315,8 @@ public SQLiteConnection (SQLiteConnectionString connectionString)
298315
_open = true;
299316

300317
StoreDateTimeAsTicks = connectionString.StoreDateTimeAsTicks;
318+
DateTimeStringFormat = connectionString.DateTimeStringFormat;
319+
DateTimeStyle = connectionString.DateTimeStyle;
301320

302321
BusyTimeout = TimeSpan.FromSeconds (0.1);
303322
Tracer = line => Debug.WriteLine (line);
@@ -546,7 +565,7 @@ public CreateTableResult CreateTable (Type ty, CreateFlags createFlags = CreateF
546565

547566
// Build query.
548567
var query = "create " + @virtual + "table if not exists \"" + map.TableName + "\" " + @using + "(\n";
549-
var decls = map.Columns.Select (p => Orm.SqlDecl (p, StoreDateTimeAsTicks));
568+
var decls = map.Columns.Select (p => Orm.SqlDecl (p, StoreDateTimeAsTicks, StoreTimeSpanAsTicks));
550569
var decl = string.Join (",\n", decls.ToArray ());
551570
query += decl;
552571
query += ")";
@@ -812,7 +831,7 @@ void MigrateTable (TableMapping map, List<ColumnInfo> existingCols)
812831
}
813832

814833
foreach (var p in toBeAdded) {
815-
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);
816835
Execute (addCol);
817836
}
818837
}
@@ -2090,9 +2109,14 @@ public enum NotifyTableChangedAction
20902109
/// </summary>
20912110
public class SQLiteConnectionString
20922111
{
2112+
const string DateTimeSqliteDefaultFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff";
2113+
20932114
public string UniqueKey { get; }
20942115
public string DatabasePath { get; }
20952116
public bool StoreDateTimeAsTicks { get; }
2117+
public bool StoreTimeSpanAsTicks { get; }
2118+
public string DateTimeStringFormat { get; }
2119+
public System.Globalization.DateTimeStyles DateTimeStyle { get; }
20962120
public object Key { get; }
20972121
public SQLiteOpenFlags OpenFlags { get; }
20982122
public Action<SQLiteConnection> PreKeyAction { get; }
@@ -2194,13 +2218,25 @@ public SQLiteConnectionString (string databasePath, bool storeDateTimeAsTicks, o
21942218
/// <param name="vfsName">
21952219
/// Specifies the Virtual File System to use on the database.
21962220
/// </param>
2197-
public SQLiteConnectionString (string databasePath, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks, object key = null, Action<SQLiteConnection> preKeyAction = null, Action<SQLiteConnection> postKeyAction = null, string vfsName = null)
2221+
/// <param name="dateTimeStringFormat">
2222+
/// Specifies the format to use when storing DateTime properties as strings.
2223+
/// </param>
2224+
/// <param name="storeTimeSpanAsTicks">
2225+
/// Specifies whether to store TimeSpan properties as ticks (true) or strings (false). You
2226+
/// absolutely do want to store them as Ticks in all new projects. The value of false is
2227+
/// only here for backwards compatibility. There is a *significant* speed advantage, with no
2228+
/// down sides, when setting storeTimeSpanAsTicks = true.
2229+
/// </param>
2230+
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)
21982231
{
21992232
if (key != null && !((key is byte[]) || (key is string)))
22002233
throw new ArgumentException ("Encryption keys must be strings or byte arrays", nameof (key));
22012234

22022235
UniqueKey = string.Format ("{0}_{1:X8}", databasePath, (uint)openFlags);
22032236
StoreDateTimeAsTicks = storeDateTimeAsTicks;
2237+
StoreTimeSpanAsTicks = storeTimeSpanAsTicks;
2238+
DateTimeStringFormat = dateTimeStringFormat;
2239+
DateTimeStyle = "o".Equals (DateTimeStringFormat, StringComparison.OrdinalIgnoreCase) || "r".Equals (DateTimeStringFormat, StringComparison.OrdinalIgnoreCase) ? System.Globalization.DateTimeStyles.RoundtripKind : System.Globalization.DateTimeStyles.None;
22042240
Key = key;
22052241
PreKeyAction = preKeyAction;
22062242
PostKeyAction = postKeyAction;
@@ -2596,9 +2632,9 @@ public static Type GetType (object obj)
25962632
return obj.GetType ();
25972633
}
25982634

2599-
public static string SqlDecl (TableMapping.Column p, bool storeDateTimeAsTicks)
2635+
public static string SqlDecl (TableMapping.Column p, bool storeDateTimeAsTicks, bool storeTimeSpanAsTicks)
26002636
{
2601-
string decl = "\"" + p.Name + "\" " + SqlType (p, storeDateTimeAsTicks) + " ";
2637+
string decl = "\"" + p.Name + "\" " + SqlType (p, storeDateTimeAsTicks, storeTimeSpanAsTicks) + " ";
26022638

26032639
if (p.IsPK) {
26042640
decl += "primary key ";
@@ -2616,7 +2652,7 @@ public static string SqlDecl (TableMapping.Column p, bool storeDateTimeAsTicks)
26162652
return decl;
26172653
}
26182654

2619-
public static string SqlType (TableMapping.Column p, bool storeDateTimeAsTicks)
2655+
public static string SqlType (TableMapping.Column p, bool storeDateTimeAsTicks, bool storeTimeSpanAsTicks)
26202656
{
26212657
var clrType = p.ColumnType;
26222658
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)
26342670
return "varchar";
26352671
}
26362672
else if (clrType == typeof (TimeSpan)) {
2637-
return "bigint";
2673+
return storeTimeSpanAsTicks ? "bigint" : "time";
26382674
}
26392675
else if (clrType == typeof (DateTime)) {
26402676
return storeDateTimeAsTicks ? "bigint" : "datetime";
@@ -2920,15 +2956,13 @@ void BindAll (Sqlite3Statement stmt)
29202956
b.Index = nextIdx++;
29212957
}
29222958

2923-
BindParameter (stmt, b.Index, b.Value, _conn.StoreDateTimeAsTicks);
2959+
BindParameter (stmt, b.Index, b.Value, _conn.StoreDateTimeAsTicks, _conn.DateTimeStringFormat, _conn.StoreTimeSpanAsTicks);
29242960
}
29252961
}
29262962

29272963
static IntPtr NegativePointer = new IntPtr (-1);
29282964

2929-
const string DateTimeExactStoreFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff";
2930-
2931-
internal static void BindParameter (Sqlite3Statement stmt, int index, object value, bool storeDateTimeAsTicks)
2965+
internal static void BindParameter (Sqlite3Statement stmt, int index, object value, bool storeDateTimeAsTicks, string dateTimeStringFormat, bool storeTimeSpanAsTicks)
29322966
{
29332967
if (value == null) {
29342968
SQLite3.BindNull (stmt, index);
@@ -2953,14 +2987,19 @@ internal static void BindParameter (Sqlite3Statement stmt, int index, object val
29532987
SQLite3.BindDouble (stmt, index, Convert.ToDouble (value));
29542988
}
29552989
else if (value is TimeSpan) {
2956-
SQLite3.BindInt64 (stmt, index, ((TimeSpan)value).Ticks);
2990+
if (storeTimeSpanAsTicks) {
2991+
SQLite3.BindInt64 (stmt, index, ((TimeSpan)value).Ticks);
2992+
}
2993+
else {
2994+
SQLite3.BindText (stmt, index, ((TimeSpan)value).ToString (), -1, NegativePointer);
2995+
}
29572996
}
29582997
else if (value is DateTime) {
29592998
if (storeDateTimeAsTicks) {
29602999
SQLite3.BindInt64 (stmt, index, ((DateTime)value).Ticks);
29613000
}
29623001
else {
2963-
SQLite3.BindText (stmt, index, ((DateTime)value).ToString (DateTimeExactStoreFormat, System.Globalization.CultureInfo.InvariantCulture), -1, NegativePointer);
3002+
SQLite3.BindText (stmt, index, ((DateTime)value).ToString (dateTimeStringFormat, System.Globalization.CultureInfo.InvariantCulture), -1, NegativePointer);
29643003
}
29653004
}
29663005
else if (value is DateTimeOffset) {
@@ -3036,7 +3075,17 @@ object ReadCol (Sqlite3Statement stmt, int index, SQLite3.ColType type, Type clr
30363075
return (float)SQLite3.ColumnDouble (stmt, index);
30373076
}
30383077
else if (clrType == typeof (TimeSpan)) {
3039-
return new TimeSpan (SQLite3.ColumnInt64 (stmt, index));
3078+
if (_conn.StoreTimeSpanAsTicks) {
3079+
return new TimeSpan (SQLite3.ColumnInt64 (stmt, index));
3080+
}
3081+
else {
3082+
var text = SQLite3.ColumnString (stmt, index);
3083+
TimeSpan resultTime;
3084+
if (!TimeSpan.TryParseExact (text, "c", System.Globalization.CultureInfo.InvariantCulture, System.Globalization.TimeSpanStyles.None, out resultTime)) {
3085+
resultTime = TimeSpan.Parse (text);
3086+
}
3087+
return resultTime;
3088+
}
30403089
}
30413090
else if (clrType == typeof (DateTime)) {
30423091
if (_conn.StoreDateTimeAsTicks) {
@@ -3045,7 +3094,7 @@ object ReadCol (Sqlite3Statement stmt, int index, SQLite3.ColType type, Type clr
30453094
else {
30463095
var text = SQLite3.ColumnString (stmt, index);
30473096
DateTime resultDate;
3048-
if (!DateTime.TryParseExact (text, DateTimeExactStoreFormat, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out resultDate)) {
3097+
if (!DateTime.TryParseExact (text, _conn.DateTimeStringFormat, System.Globalization.CultureInfo.InvariantCulture, _conn.DateTimeStyle, out resultDate)) {
30493098
resultDate = DateTime.Parse (text);
30503099
}
30513100
return resultDate;
@@ -3149,7 +3198,7 @@ public int ExecuteNonQuery (object[] source)
31493198
//bind the values.
31503199
if (source != null) {
31513200
for (int i = 0; i < source.Length; i++) {
3152-
SQLiteCommand.BindParameter (Statement, i + 1, source[i], Connection.StoreDateTimeAsTicks);
3201+
SQLiteCommand.BindParameter (Statement, i + 1, source[i], Connection.StoreDateTimeAsTicks, Connection.DateTimeStringFormat, Connection.StoreTimeSpanAsTicks);
31533202
}
31543203
}
31553204
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/DateTimeTest.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,15 @@ public void AsTicks ()
3535
[Test]
3636
public void AsStrings ()
3737
{
38-
var db = new TestDb (storeDateTimeAsTicks: false);
38+
var db = new TestDb (storeDateTimeAsTicks: false);
39+
TestDateTime (db);
40+
}
41+
42+
[TestCase ("o")]
43+
[TestCase ("MMM'-'dd'-'yyyy' 'HH':'mm':'ss'.'fffffff")]
44+
public void AsCustomStrings (string format)
45+
{
46+
var db = new TestDb (CustomDateTimeString (format));
3947
TestDateTime (db);
4048
}
4149

@@ -53,6 +61,16 @@ public void AsyncAsString ()
5361
TestAsyncDateTime (db);
5462
}
5563

64+
[TestCase ("o")]
65+
[TestCase ("MMM'-'dd'-'yyyy' 'HH':'mm':'ss'.'fffffff")]
66+
public void AsyncAsCustomStrings (string format)
67+
{
68+
var db = new SQLiteAsyncConnection (CustomDateTimeString (format));
69+
TestAsyncDateTime (db);
70+
}
71+
72+
SQLiteConnectionString CustomDateTimeString (string dateTimeFormat) => new SQLiteConnectionString (TestPath.GetTempFileName (), false, dateTimeFormat);
73+
5674
void TestAsyncDateTime (SQLiteAsyncConnection db)
5775
{
5876
db.CreateTableAsync<TestObj> ().Wait ();

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/TestDb.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@ public TestDb (bool storeDateTimeAsTicks = true, object key = null, bool wal = t
6060
EnableWriteAheadLogging ();
6161
}
6262

63+
public TestDb (SQLiteConnectionString connectionString, bool wal = true) : base (connectionString)
64+
{
65+
Trace = true;
66+
if (wal)
67+
EnableWriteAheadLogging ();
68+
}
69+
6370
public TestDb (string path, bool storeDateTimeAsTicks = true, object key = null, bool wal = true) : base (new SQLiteConnectionString (path, storeDateTimeAsTicks, key: key))
6471
{
6572
Trace = true;

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)