以前在处理DateTime的时候,为了屏蔽不同数据库的差异,经常将DateTime保存成字符串,只要服务器不切换本地化表示方法,这种方案肯定是没有问题的。只是在读取和保存的时候需要各做一次转换,(实际上System.Data.Sqlite也会做类似的事情,因此性能上几乎没有差异)。
这种方法延续到Sqlite一样没有问题,最近看一个开源项目,里面直接用的DateTime,里面用了Utc格式,后来在日志中发现总是有格式异常的报警,仔细调阅了一下日志发现问题都是一样的,根源指到了System.Data.Sqlite。
System.FormatException: String was not recognized as a valid DateTime.
at System.DateTimeParse.ParseExactMultiple(String s, String[] formats, DateTimeFormatInfo dtfi, DateTimeStyles style)
at System.DateTime.ParseExact(String s, String[] formats, IFormatProvider provider, DateTimeStyles style)
at System.Data.SQLite.SQLiteConvert.ToDateTime(String dateText)
at System.Data.SQLite.SQLite3.GetDateTime(SQLiteStatement stmt, Int32 index)
at System.Data.SQLite.SQLite3.GetValue(SQLiteStatement stmt, Int32 index, SQLiteType typ)
at System.Data.SQLite.SQLiteDataReader.GetValue(Int32 i)
对应源代码为:
Sqlite3.cs
internal override DateTime GetDateTime(SQLiteStatement stmt, int index) { #if !SQLITE_STANDARD int len; return ToDateTime(UnsafeNativeMethods.sqlite3_column_text_interop(stmt._sqlite_stmt, index, out len), len); #else return ToDateTime(UnsafeNativeMethods.sqlite3_column_text(stmt._sqlite_stmt, index), - 1 ); #endif }
SQLiteConvert.cs
/// <summary> /// Converts a string into a DateTime, using the current DateTimeFormat specified for the connection when it was opened. /// </summary> /// <remarks> /// Acceptable ISO8601 DateTime formats are: /// yyyy-MM-dd HH:mm:ss /// yyyyMMddHHmmss /// yyyyMMddTHHmmssfffffff /// yyyy-MM-dd /// yy-MM-dd /// yyyyMMdd /// HH:mm:ss /// THHmmss /// </remarks> /// <param name="dateText"> The string containing either a Tick value, a JulianDay double, or an ISO8601-format string </param> /// <returns> A DateTime value </returns> public DateTime ToDateTime( string dateText) { switch (_datetimeFormat) { case SQLiteDateFormats.Ticks: return new DateTime(Convert.ToInt64(dateText, CultureInfo.InvariantCulture)); case SQLiteDateFormats.JulianDay: return ToDateTime(Convert.ToDouble(dateText, CultureInfo.InvariantCulture)); default : return DateTime.ParseExact(dateText, _datetimeFormats, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None); } }
问题就出现在这里面,ISO8601规范,无非就有两种办法,
一种是在存储的时候datetime格式要遵守ISO8601格式规范,即转码.ToString("S");
另外一种方法就是扩展_datetimeFormats,让其支持其他格式。
原始定义为:
/// <summary> /// An array of ISO8601 datetime formats we support conversion from /// </summary> private static string [] _datetimeFormats = new string [] { " THHmmss " , " THHmm " , " HH:mm:ss " , " HH:mm " , " HH:mm:ss.FFFFFFF " , " yy-MM-dd " , " yyyy-MM-dd " , " yyyy-MM-dd HH:mm:ss.FFFFFFF " , " yyyy-MM-dd HH:mm:ss " , " yyyy-MM-dd HH:mm " , " yyyy-MM-ddTHH:mm:ss.FFFFFFF " , " yyyy-MM-ddTHH:mm " , " yyyy-MM-ddTHH:mm:ss " , " yyyyMMddHHmmss " , " yyyyMMddHHmm " , " yyyyMMddTHHmmssFFFFFFF " , " yyyyMMdd " };
修改后的定义为:
/// <summary> /// An array of ISO8601 datetime formats we support conversion from /// </summary> private static string [] _datetimeFormats = new string [] { " THHmmss " , " THHmm " , " HH:mm:ss " , " HH:mm " , " HH:mm:ss.FFFFFFF " , " yy-M-d " , " yy-M-dd " , " yy-MM-dd " , " yyyy-M-d " , " yyyy-M-dd " , " yyyy-MM-dd " , " yyyy-M-d H:mm:ss.FFFFFFF " , " yyyy-M-d H:mm:ss " , " yyyy-M-d H:mm " , " yyyy-M-d HH:mm:ss.FFFFFFF " , " yyyy-M-d HH:mm:ss " , " yyyy-M-d HH:mm " , " yyyy-M-dTHH:mm:ss.FFFFFFF " , " yyyy-M-dTHH:mm " , " yyyy-M-dTHH:mm:ss " , " yyyy-M-dd H:mm:ss.FFFFFFF " , " yyyy-M-dd H:mm:ss " , " yyyy-M-dd H:mm " , " yyyy-M-dd HH:mm:ss.FFFFFFF " , " yyyy-M-dd HH:mm:ss " , " yyyy-M-dd HH:mm " , " yyyy-M-ddTHH:mm:ss.FFFFFFF " , " yyyy-M-ddTHH:mm " , " yyyy-M-ddTHH:mm:ss " , " yyyy-MM-dd H:mm:ss.FFFFFFF " , " yyyy-MM-dd H:mm:ss " , " yyyy-MM-dd H:mm " , " yyyy-MM-dd HH:mm:ss.FFFFFFF " , " yyyy-MM-dd HH:mm:ss " , " yyyy-MM-dd HH:mm " , " yyyy-MM-ddTHH:mm:ss.FFFFFFF " , " yyyy-MM-ddTHH:mm " , " yyyy-MM-ddTHH:mm:ss " , " yyyyMMddHHmmss " , " yyyyMMddHHmm " , " yyyyMMddTHHmmssFFFFFFF " , " yyyyMMdd " , " yy/M/d " , " yy/M/dd " , " yy/MM/dd " , " yyyy/M/d " , " yyyy/M/dd " , " yyyy/MM/dd " , " yyyy/M/d H:mm:ss.FFFFFFF " , " yyyy/M/d H:mm:ss " , " yyyy/M/d H:mm " , " yyyy/M/d HH:mm:ss.FFFFFFF " , " yyyy/M/d HH:mm:ss " , " yyyy/M/d HH:mm " , " yyyy/M/dTHH:mm:ss.FFFFFFF " , " yyyy/M/dTHH:mm " , " yyyy/M/dTHH:mm:ss " , " yyyy/M/dd H:mm:ss.FFFFFFF " , " yyyy/M/dd H:mm:ss " , " yyyy/M/dd H:mm " , " yyyy/M/dd HH:mm:ss.FFFFFFF " , " yyyy/M/dd HH:mm:ss " , " yyyy/M/dd HH:mm " , " yyyy/M/ddTHH:mm:ss.FFFFFFF " , " yyyy/M/ddTHH:mm " , " yyyy/M/ddTHH:mm:ss " , " yyyy/MM/dd H:mm:ss.FFFFFFF " , " yyyy/MM/dd H:mm:ss " , " yyyy/MM/dd H:mm " , " yyyy/MM/dd HH:mm:ss.FFFFFFF " , " yyyy/MM/dd HH:mm:ss " , " yyyy/MM/dd HH:mm " , " yyyy/MM/ddTHH:mm:ss.FFFFFFF " , " yyyy/MM/ddTHH:mm " , " yyyy/MM/ddTHH:mm:ss " };
然后重新编译一下,就OK了,您也可以根据具体的使用情况进行扩展。
最后还有一种办法,就是Ticks,这种方案需要在连接字符串上设置,DateTimeFormat=Ticks(默认是ISO8601),这种方案和最开始的方案类似,第一种方案是转换为字符串,这种是转换为int64,然后由系统的转换方法实现转换。
修改后的Sqlite,