Index: Doc/Extra/Provider/version.html
==================================================================
--- Doc/Extra/Provider/version.html
+++ Doc/Extra/Provider/version.html
@@ -47,10 +47,11 @@
- Updated to SQLite 3.8.7.1.
- Make sure SQL statements generated for DbUpdateCommandTree objects are properly delimited.
- Various minor performance enhancements to the SQLiteDataReader class. Pursuant to [e122d26e70].
- In the SQLiteDataReader.VerifyType method, remove duplicate "if" statement for the DbType.SByte value and move the remaining "if" to the Int64 affinity. Fix for [c5cc2fb334]. ** Potentially Incompatible Change **
+ - Handle Julian Day values that fall outside of the supported range for OLE Automation dates. Fix for [3e783eecbe]. ** Potentially Incompatible Change **
1.0.94.0 - September 9, 2014
- Updated to SQLite 3.8.6.
- Updated to Entity Framework 6.1.1.
Index: System.Data.SQLite/SQLiteConvert.cs
==================================================================
--- System.Data.SQLite/SQLiteConvert.cs
+++ System.Data.SQLite/SQLiteConvert.cs
@@ -39,20 +39,35 @@
/// The value for the Unix epoch (e.g. January 1, 1970 at midnight, in UTC).
///
protected static readonly DateTime UnixEpoch =
new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+ #pragma warning disable 414
///
- /// The value of the OLE Automation epoch represented as a Julian day.
+ /// The value of the OLE Automation epoch represented as a Julian day. This
+ /// field cannot be removed as the test suite relies upon it.
///
private static readonly double OleAutomationEpochAsJulianDay = 2415018.5;
+ #pragma warning restore 414
///
/// The format string for DateTime values when using the InvariantCulture or CurrentCulture formats.
///
private const string FullFormat = "yyyy-MM-ddTHH:mm:ss.fffffffK";
+ ///
+ /// This is the minimum Julian Day value supported by this library
+ /// (148731163200000).
+ ///
+ private static readonly long MinimumJd = computeJD(DateTime.MinValue);
+
+ ///
+ /// This is the maximum Julian Day value supported by this library
+ /// (464269060799000).
+ ///
+ private static readonly long MaximumJd = computeJD(DateTime.MaxValue);
+
///
/// An array of ISO-8601 DateTime formats that we support parsing.
///
private static string[] _datetimeFormats = new string[] {
"THHmmssK",
@@ -199,15 +214,273 @@
Marshal.Copy(nativestring, byteArray, 0, nativestringlen);
return _utf8.GetString(byteArray, 0, nativestringlen);
}
-
-
#endregion
+
+ ///////////////////////////////////////////////////////////////////////////
#region DateTime Conversion Functions
+ #region New Julian Day Conversion Methods
+ ///
+ /// Checks if the specified is within the
+ /// supported range for a Julian Day value.
+ ///
+ ///
+ /// The Julian Day value to check.
+ ///
+ ///
+ /// Non-zero if the specified Julian Day value is in the supported
+ /// range; otherwise, zero.
+ ///
+ private static bool isValidJd(
+ long jd
+ )
+ {
+ return ((jd >= MinimumJd) && (jd <= MaximumJd));
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// Converts a Julian Day value from a to an
+ /// .
+ ///
+ ///
+ /// The Julian Day value to convert.
+ ///
+ ///
+ /// The resulting Julian Day value.
+ ///
+ private static long DoubleToJd(
+ double julianDay
+ )
+ {
+ return (long)(julianDay * 86400000.0);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// Converts a Julian Day value from an to a
+ /// .
+ ///
+ ///
+ /// The Julian Day value to convert.
+ ///
+ ///
+ /// The resulting Julian Day value.
+ ///
+ private static double JdToDouble(
+ long jd
+ )
+ {
+ return (double)(jd / 86400000.0);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// Converts a Julian Day value to a .
+ /// This method was translated from the "computeYMD" function in the
+ /// "date.c" file belonging to the SQLite core library.
+ ///
+ ///
+ /// The Julian Day value to convert.
+ ///
+ ///
+ /// The value to return in the event that the
+ /// Julian Day is out of the supported range. If this value is null,
+ /// an exception will be thrown instead.
+ ///
+ ///
+ /// A value that contains the year, month, and
+ /// day values that are closest to the specified Julian Day value.
+ ///
+ private static DateTime computeYMD(
+ long jd,
+ DateTime? badValue
+ )
+ {
+ if (!isValidJd(jd))
+ {
+ if (badValue == null)
+ {
+ throw new ArgumentException(
+ "Not a supported Julian Day value.");
+ }
+
+ return (DateTime)badValue;
+ }
+
+ int Z, A, B, C, D, E, X1;
+
+ Z = (int)((jd + 43200000) / 86400000);
+ A = (int)((Z - 1867216.25) / 36524.25);
+ A = Z + 1 + A - (A / 4);
+ B = A + 1524;
+ C = (int)((B - 122.1) / 365.25);
+ D = (36525 * C) / 100;
+ E = (int)((B - D) / 30.6001);
+ X1 = (int)(30.6001 * E);
+
+ int day, month, year;
+
+ day = B - D - X1;
+ month = E < 14 ? E - 1 : E - 13;
+ year = month > 2 ? C - 4716 : C - 4715;
+
+ try
+ {
+ return new DateTime(year, month, day);
+ }
+ catch
+ {
+ if (badValue == null)
+ throw;
+
+ return (DateTime)badValue;
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// Converts a Julian Day value to a .
+ /// This method was translated from the "computeHMS" function in the
+ /// "date.c" file belonging to the SQLite core library.
+ ///
+ ///
+ /// The Julian Day value to convert.
+ ///
+ ///
+ /// The value to return in the event that the
+ /// Julian Day value is out of the supported range. If this value is
+ /// null, an exception will be thrown instead.
+ ///
+ ///
+ /// A value that contains the hour, minute, and
+ /// second, and millisecond values that are closest to the specified
+ /// Julian Day value.
+ ///
+ private static DateTime computeHMS(
+ long jd,
+ DateTime? badValue
+ )
+ {
+ if (!isValidJd(jd))
+ {
+ if (badValue == null)
+ {
+ throw new ArgumentException(
+ "Not a supported Julian Day value.");
+ }
+
+ return (DateTime)badValue;
+ }
+
+ int si;
+
+ si = (int)((jd + 43200000) % 86400000);
+
+ decimal sd;
+
+ sd = si / 1000.0M;
+ si = (int)sd;
+
+ int millisecond = (int)((sd - si) * 1000.0M);
+
+ sd -= si;
+
+ int hour;
+
+ hour = si / 3600;
+ si -= hour * 3600;
+
+ int minute;
+
+ minute = si / 60;
+ sd += si - minute * 60;
+
+ int second = (int)sd;
+
+ try
+ {
+ DateTime minValue = DateTime.MinValue;
+
+ return new DateTime(
+ minValue.Year, minValue.Month, minValue.Day,
+ hour, minute, second, millisecond);
+ }
+ catch
+ {
+ if (badValue == null)
+ throw;
+
+ return (DateTime)badValue;
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// Converts a to a Julian Day value.
+ /// This method was translated from the "computeJD" function in
+ /// the "date.c" file belonging to the SQLite core library.
+ /// Since the range of Julian Day values supported by this method
+ /// includes all possible (valid) values of a
+ /// value, it should be extremely difficult for this method to
+ /// raise an exception or return an undefined result.
+ ///
+ ///
+ /// The value to convert. This value
+ /// will be within the range of
+ /// (00:00:00.0000000, January 1, 0001) to
+ /// (23:59:59.9999999, December
+ /// 31, 9999).
+ ///
+ ///
+ /// The nearest Julian Day value corresponding to the specified
+ /// value.
+ ///
+ private static long computeJD(
+ DateTime dateTime
+ )
+ {
+ int Y, M, D;
+
+ Y = dateTime.Year;
+ M = dateTime.Month;
+ D = dateTime.Day;
+
+ if (M <= 2)
+ {
+ Y--;
+ M += 12;
+ }
+
+ int A, B, X1, X2;
+
+ A = Y / 100;
+ B = 2 - A + (A / 4);
+ X1 = 36525 * (Y + 4716) / 100;
+ X2 = 306001 * (M + 1) / 10000;
+
+ long jd;
+
+ jd = (long)((X1 + X2 + D + B - 1524.5) * 86400000);
+
+ jd += (dateTime.Hour * 3600000) + (dateTime.Minute * 60000) +
+ (dateTime.Second * 1000) + dateTime.Millisecond;
+
+ return jd;
+ }
+ #endregion
+
+ ///////////////////////////////////////////////////////////////////////////
+
///
/// Converts a string into a DateTime, using the DateTimeFormat, DateTimeKind,
/// and DateTimeFormatString specified for the connection when it was opened.
///
///
@@ -406,14 +679,23 @@
/// Converts a julianday value into a DateTime
///
/// The value to convert
/// The DateTimeKind to use.
/// A .NET DateTime
- public static DateTime ToDateTime(double julianDay, DateTimeKind kind)
+ public static DateTime ToDateTime(
+ double julianDay,
+ DateTimeKind kind
+ )
{
- return DateTime.SpecifyKind(
- DateTime.FromOADate(julianDay - OleAutomationEpochAsJulianDay), kind);
+ long jd = DoubleToJd(julianDay);
+ DateTime dateTimeYMD = computeYMD(jd, null);
+ DateTime dateTimeHMS = computeHMS(jd, null);
+
+ return new DateTime(
+ dateTimeYMD.Year, dateTimeYMD.Month, dateTimeYMD.Day,
+ dateTimeHMS.Hour, dateTimeHMS.Minute, dateTimeHMS.Second,
+ dateTimeHMS.Millisecond, kind);
}
///
/// Converts the specified number of seconds from the Unix epoch into a
/// value.
@@ -455,11 +737,11 @@
///
/// The DateTime to convert
/// The JulianDay value the Datetime represents
public static double ToJulianDay(DateTime value)
{
- return value.ToOADate() + OleAutomationEpochAsJulianDay;
+ return JdToDouble(computeJD(value));
}
///
/// Converts a DateTime struct to the whole number of seconds since the
/// Unix epoch.
ADDED Tests/tkt-3e783eecbe.eagle
Index: Tests/tkt-3e783eecbe.eagle
==================================================================
--- /dev/null
+++ Tests/tkt-3e783eecbe.eagle
@@ -0,0 +1,166 @@
+###############################################################################
+#
+# tkt-3e783eecbe.eagle --
+#
+# Written by Joe Mistachkin.
+# Released to the public domain, use at your own risk!
+#
+###############################################################################
+
+package require Eagle
+package require Eagle.Library
+package require Eagle.Test
+
+runTestPrologue
+
+###############################################################################
+
+package require System.Data.SQLite.Test
+runSQLiteTestPrologue
+
+###############################################################################
+
+runTest {test tkt-3e783eecbe-1.1 {To Julian Day w/o OLE Automation} -setup {
+ set minDateTime [object invoke -create -alias DateTime MinValue]
+ set maxDateTime [object invoke -create -alias DateTime MaxValue]
+
+ set dateTimeValues [list $minDateTime "0099-12-31 00:00:00" \
+ "0100-01-01 00:00:00" $maxDateTime]
+} -body {
+ set results [list]
+
+ foreach dateTimeValue $dateTimeValues {
+ if {$dateTimeValue ni [info objects]} then {
+ set dateTimeValue [object invoke \
+ -create DateTime ParseExact $dateTimeValue \
+ [getDateTimeFormat] null]
+
+ set dateTimeValue [object invoke -create -alias \
+ DateTime SpecifyKind $dateTimeValue Utc]
+ }
+
+ set code [catch {
+ object invoke System.Data.SQLite.SQLiteConvert ToJulianDay \
+ $dateTimeValue
+ } result]
+
+ if {$code != 0} then {
+ regexp -- {---> (.*?):} $result result result
+ regexp -- {(System\..*?):} $result result result
+ }
+
+ lappend results [list [$dateTimeValue ToString [getDateTimeFormat]] \
+ $code $result]
+ }
+
+ set results
+} -cleanup {
+ unset -nocomplain code result results dateTimeValue dateTimeValues \
+ maxDateTime minDateTime
+} -constraints {eagle System.Data.SQLite} -result {{{0001-01-01 00:00:00} 0\
+1721425.5} {{0099-12-31 00:00:00Z} 0 1757583.5} {{0100-01-01 00:00:00Z} 0\
+1757584.5} {{9999-12-31 23:59:59.9999999} 0 5373484.49999999}}}
+
+###############################################################################
+
+runTest {test tkt-3e783eecbe-1.2 {From Julian Day w/o OLE Automation} -setup {
+ set minDoubleValue [object invoke -create -alias Double MinValue]
+ set maxDoubleValue [object invoke -create -alias Double MaxValue]
+
+ set doubleValues [list \
+ $minDoubleValue -1.0 0.0 1.0 1721425.5 1757583.5 2451910.5 \
+ 2456962.0 5373484.49999998 5373484.49999999 $maxDoubleValue]
+} -body {
+ set results [list]
+
+ foreach doubleValue $doubleValues {
+ if {$doubleValue ni [info objects]} then {
+ set doubleValue [object invoke \
+ -create -alias Double Parse $doubleValue]
+ }
+
+ set code [catch {
+ set dateTimeValue [object invoke -create -alias \
+ System.Data.SQLite.SQLiteConvert ToDateTime $doubleValue Utc]
+
+ $dateTimeValue ToString [getDateTimeFormat]
+ } result]
+
+ if {$code != 0} then {
+ regexp -- {---> (.*?):} $result result result
+ regexp -- {(System\..*?):} $result result result
+ }
+
+ lappend results [list [$doubleValue ToString] $code $result]
+ }
+
+ set results
+} -cleanup {
+ unset -nocomplain code result results dateTimeValue doubleValue \
+ doubleValues maxDoubleValue minDoubleValue
+} -constraints {eagle System.Data.SQLite} -result {{-1.79769313486232E+308 1\
+System.ArgumentException} {-1 1 System.ArgumentException} {0 1\
+System.ArgumentException} {1 1 System.ArgumentException} {1721425.5 0\
+{0001-01-01 00:00:00Z}} {1757583.5 0 {0099-12-31 00:00:00Z}} {2451910.5 0\
+{2001-01-01 00:00:00Z}} {2456962 0 {2014-10-31 12:00:00Z}} {5373484.49999998 0\
+{9999-12-31 23:59:59.998Z}} {5373484.49999999 0 {9999-12-31 23:59:59.999Z}}\
+{1.79769313486232E+308 1 System.ArgumentException}}}
+
+###############################################################################
+
+runTest {test tkt-3e783eecbe-1.3 {Julian Day storage} -setup {
+ set doubleValues [list \
+ -1.0 0.0 1.0 1721425.5 1757583.5 2451910.5 2456962.0 \
+ 5373484.49999998 5373484.49999999]
+
+ setupDb [set fileName tkt-3e783eecbe-1.3.db] "" JulianDay
+} -body {
+ sql execute $db "CREATE TABLE t1(x INTEGER, y DATETIME);"
+
+ set i 1
+ set results [list]
+
+ foreach doubleValue $doubleValues {
+ if {$doubleValue ni [info objects]} then {
+ set doubleValue [object invoke \
+ -create -alias Double Parse $doubleValue]
+ }
+
+ set code [catch {
+ sql execute $db "INSERT INTO t1 (x, y) VALUES(?, ?);" \
+ [list param1 Int32 $i] [list param2 Double $doubleValue]
+
+ sql execute -execute reader -format list -datetimeformat \
+ [getDateTimeFormat] $db "SELECT y FROM t1 WHERE x = ?;" \
+ [list param1 Int32 $i]
+ } result]
+
+ if {$code != 0} then {
+ regexp -- {---> (.*?):} $result result result
+ regexp -- {(System\..*?):} $result result result
+ }
+
+ lappend results [list [$doubleValue ToString] $code $result]
+ incr i
+ }
+
+ set results
+} -cleanup {
+ unset -nocomplain code result results i dateTimeValue doubleValue \
+ doubleValues maxDoubleValue minDoubleValue
+
+ cleanupDb $fileName
+
+ unset -nocomplain db fileName
+} -constraints \
+{eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite} -result \
+{{-1 1 System.ArgumentException} {0 1 System.ArgumentException} {1 1\
+System.ArgumentException} {1721425.5 0 {{0001-01-01 00:00:00}}} {1757583.5 0\
+{{0099-12-31 00:00:00}}} {2451910.5 0 {{2001-01-01 00:00:00}}} {2456962 0\
+{{2014-10-31 12:00:00}}} {5373484.49999998 0 {{9999-12-31 23:59:59.998}}}\
+{5373484.49999999 0 {{9999-12-31 23:59:59.999}}}}}
+
+###############################################################################
+
+runSQLiteTestEpilogue
+runTestEpilogue
Index: readme.htm
==================================================================
--- readme.htm
+++ readme.htm
@@ -214,10 +214,11 @@
- Updated to SQLite 3.8.7.
- Make sure SQL statements generated for DbUpdateCommandTree objects are properly delimited.
- Various minor performance enhancements to the SQLiteDataReader class. Pursuant to [e122d26e70].
- In the SQLiteDataReader.VerifyType method, remove duplicate "if" statement for the DbType.SByte value and move the remaining "if" to the Int64 affinity. Fix for [c5cc2fb334]. ** Potentially Incompatible Change **
+ - Handle Julian Day values that fall outside of the supported range for OLE Automation dates. Fix for [3e783eecbe]. ** Potentially Incompatible Change **
1.0.94.0 - September 9, 2014
Index: www/news.wiki
==================================================================
--- www/news.wiki
+++ www/news.wiki
@@ -8,10 +8,11 @@
- Updated to [http://www.sqlite.org/releaselog/3_8_7_1.html|SQLite 3.8.7.1].
- Make sure SQL statements generated for DbUpdateCommandTree objects are properly delimited.
- Various minor performance enhancements to the SQLiteDataReader class. Pursuant to [e122d26e70].
- In the SQLiteDataReader.VerifyType method, remove duplicate "if" statement for the DbType.SByte value and move the remaining "if" to the Int64 affinity. Fix for [c5cc2fb334]. ** Potentially Incompatible Change **
+ - Handle Julian Day values that fall outside of the supported range for OLE Automation dates. Fix for [3e783eecbe]. ** Potentially Incompatible Change **
1.0.94.0 - September 9, 2014