diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml
index f4459bdda9..fe8afbdfad 100644
--- a/doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml
+++ b/doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml
@@ -634,6 +634,25 @@
]]>
+
+ The zero-based column ordinal.
+
+ Gets the value of the specified column as a .
+
+
+ The value of the column expressed as a .
+
+
+
+
+
+
+
The zero-based column ordinal.
Gets the value of the specified column as a .
diff --git a/doc/snippets/Microsoft.Data.SqlTypes/SqlDateTime2.xml b/doc/snippets/Microsoft.Data.SqlTypes/SqlDateTime2.xml
new file mode 100644
index 0000000000..d5721cd9e6
--- /dev/null
+++ b/doc/snippets/Microsoft.Data.SqlTypes/SqlDateTime2.xml
@@ -0,0 +1,72 @@
+
+
+
+ Exposes SQL Server data that is stored as a DATETIME2.
+
+
+ class is used to work with `DATETIME2` data.
+`DATETIME2`has a wider range than DATETIME and thus needs its own type.
+
+]]>
+
+
+
+
+ Whether or not this instance should be considered null - true means null, false not null
+
+ Initializes a new instance of the struct.
+ Will initialize the datetime value to 0.
+
+
+
+ Number of ticks to initialize this instance with - in the range of &
+
+
+ Initializes a new instance of the class.
+
+
+ is not in the range of &
+
+
+
+
+ Gets the representation of this instance.
+
+
+ If this instance is null
+
+
+
+
+ Gets an instance representing the value NULL from the database.
+
+
+
+
+ Converts a DateTime into a SqlDateTime2
+
+
+
+
+ Converts a DBNull instance into a Null SqlDateTime2
+
+
+
+
+ Converts a SqlDateTime2 into DateTime
+
+
+ If the SqlDateTime2 instance has a null value
+
+
+
+
+ returns a for serialization purposes
+
+ unused parameter
+
+
+
diff --git a/src/Microsoft.Data.SqlClient.sln b/src/Microsoft.Data.SqlClient.sln
index 855baeedaa..3de749056c 100644
--- a/src/Microsoft.Data.SqlClient.sln
+++ b/src/Microsoft.Data.SqlClient.sln
@@ -159,6 +159,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.Data.SqlClient.Se
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.Data.SqlTypes", "Microsoft.Data.SqlTypes", "{5A7600BD-AED8-44AB-8F2A-7CB33A8D9C02}"
ProjectSection(SolutionItems) = preProject
+ ..\doc\snippets\Microsoft.Data.SqlTypes\SqlDateTime2.xml = ..\doc\snippets\Microsoft.Data.SqlTypes\SqlDateTime2.xml
..\doc\snippets\Microsoft.Data.SqlTypes\SqlFileStream.xml = ..\doc\snippets\Microsoft.Data.SqlTypes\SqlFileStream.xml
EndProjectSection
EndProject
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Common/src/Microsoft/Data/Common/AdapterUtil.cs b/src/Microsoft.Data.SqlClient/netcore/src/Common/src/Microsoft/Data/Common/AdapterUtil.cs
index 396d43caa6..6cbea1e8c1 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Common/src/Microsoft/Data/Common/AdapterUtil.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Common/src/Microsoft/Data/Common/AdapterUtil.cs
@@ -527,5 +527,10 @@ internal static void SetCurrentTransaction(Transaction transaction)
{
Transaction.Current = transaction;
}
+
+ static internal Exception WrongType(Type got, Type expected)
+ {
+ return Argument(StringsHelper.GetString(Strings.SQL_WrongType, got.ToString(), expected.ToString()));
+ }
}
}
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
index 3e7db7d945..6b05bb4ab4 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
@@ -464,6 +464,7 @@
+
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBuffer.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBuffer.cs
index 1099c974ba..98ac4f1d89 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBuffer.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBuffer.cs
@@ -615,6 +615,40 @@ internal SqlDateTime SqlDateTime
}
}
+ internal SqlDateTime2 SqlDateTime2
+ {
+ get
+ {
+ if (IsNull)
+ {
+ return SqlDateTime2.Null;
+ }
+
+ if (StorageType.DateTime2 == _type)
+ {
+ return new SqlDateTime2(GetTicksFromDateTime2Info(_value._dateTime2Info));
+ }
+
+ if (StorageType.DateTime == _type)
+ {
+ // also handle DATETIME without boxing to object first
+ var dateTime = SqlTypeWorkarounds.SqlDateTimeToDateTime(_value._dateTimeInfo._daypart, _value._dateTimeInfo._timepart);
+
+ return new SqlDateTime2(dateTime.Ticks);
+ }
+
+ if (StorageType.Date == _type)
+ {
+ return (SqlDateTime2)DateTime.MinValue.AddDays(_value._int32);
+ }
+
+ // cannot use SqlValue, since that causes invalid cast exception since object cannot be dynamic cast to SqlDateTime2 - only explicit cast
+ // (SqlDateTime2)(object)dateTimeValue;
+ // So assume its called on some kind of DATE type, so DateTime property can handle it
+ return (SqlDateTime2)DateTime;
+ }
+ }
+
internal SqlDecimal SqlDecimal
{
get
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs
index 83fa69a74f..ed89108565 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs
@@ -2397,6 +2397,13 @@ virtual public SqlDateTime GetSqlDateTime(int i)
return _data[i].SqlDateTime;
}
+ ///
+ virtual public SqlDateTime2 GetSqlDateTime2(int i)
+ {
+ ReadColumn(i);
+ return _data[i].SqlDateTime2;
+ }
+
///
virtual public SqlDecimal GetSqlDecimal(int i)
{
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
index cf8c458f2e..04c999129f 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
@@ -103,6 +103,9 @@
Microsoft\Data\Common\ActivityCorrelator.cs
+
+ Microsoft\Data\SqlTypes\SqlDateTime2.cs
+
Microsoft\Data\Common\DbConnectionPoolKey.cs
@@ -368,10 +371,16 @@
-
-
+
+ Component
+
+
+ Component
+
-
+
+ Component
+
@@ -379,7 +388,9 @@
-
+
+ Component
+
@@ -443,7 +454,9 @@
-
+
+ Component
+
@@ -545,4 +558,4 @@
-
+
\ No newline at end of file
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlBuffer.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlBuffer.cs
index 66071812dc..53198b88d7 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlBuffer.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlBuffer.cs
@@ -597,6 +597,40 @@ internal SqlDateTime SqlDateTime
}
}
+ internal SqlDateTime2 SqlDateTime2
+ {
+ get
+ {
+ if (IsNull)
+ {
+ return SqlDateTime2.Null;
+ }
+
+ if (StorageType.DateTime2 == _type)
+ {
+ return new SqlDateTime2(GetTicksFromDateTime2Info(_value._dateTime2Info));
+ }
+
+ if (StorageType.DateTime == _type)
+ {
+ // also handle DATETIME without boxing to object first
+ var dateTime = SqlTypeWorkarounds.SqlDateTimeToDateTime(_value._dateTimeInfo._daypart, _value._dateTimeInfo._timepart);
+
+ return new SqlDateTime2(dateTime.Ticks);
+ }
+
+ if (StorageType.Date == _type)
+ {
+ return (SqlDateTime2)DateTime.MinValue.AddDays(_value._int32);
+ }
+
+ // cannot use SqlValue, since that causes invalid cast exception since object cannot be dynamic cast to SqlDateTime2 - only explicit cast
+ // (SqlDateTime2)(object)dateTimeValue;
+ // So assume its called on some kind of DATE type, so DateTime property can handle it
+ return (SqlDateTime2)DateTime;
+ }
+ }
+
internal SqlDecimal SqlDecimal
{
get
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReader.cs
index 176b3ddf3c..445a7020cd 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReader.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReader.cs
@@ -2777,6 +2777,13 @@ virtual public SqlDateTime GetSqlDateTime(int i)
return _data[i].SqlDateTime;
}
+ ///
+ virtual public SqlDateTime2 GetSqlDateTime2(int i)
+ {
+ ReadColumn(i);
+ return _data[i].SqlDateTime2;
+ }
+
///
virtual public SqlDecimal GetSqlDecimal(int i)
{
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlDateTime2.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlDateTime2.cs
new file mode 100644
index 0000000000..1a2dc0b859
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlDateTime2.cs
@@ -0,0 +1,134 @@
+using System;
+using System.Data.SqlTypes;
+using System.Runtime.InteropServices;
+using System.Xml;
+using System.Xml.Schema;
+using System.Xml.Serialization;
+using Microsoft.Data.Common;
+
+namespace Microsoft.Data.SqlTypes
+{
+ ///
+ [XmlSchemaProvider("GetXsdType")]
+ [Serializable]
+ [StructLayout(LayoutKind.Sequential)]
+ public struct SqlDateTime2 : INullable, IComparable, IEquatable, IComparable, IXmlSerializable
+ {
+ private const long _minTicks = 0; // DateTime.MinValue.Ticks
+ private const long _maxTicks = 3155378975999999999; // DateTime.MaxValue.Ticks
+
+ private bool _notNull; // false if null - has to be _notNull and not _isNull - otherwise default(SqlDateTime2) will return a SqlDateTime2 with _isNull=false and that would make XmlSerialization fail
+ private long _ticks; // Ticks representation similar to DateTime.Ticks
+
+ ///
+ private SqlDateTime2(bool isNull)
+ {
+ _notNull = !isNull;
+ _ticks = 0;
+ }
+
+ ///
+ public SqlDateTime2(long ticks)
+ {
+ if (ticks < _minTicks || ticks > _maxTicks)
+ {
+ throw ADP.ArgumentOutOfRange(nameof(ticks));
+ }
+ _notNull = true;
+ _ticks = ticks;
+ }
+
+ ///
+ public bool IsNull => !_notNull;
+
+ ///
+ public DateTime Value => _notNull ? new DateTime(_ticks) : throw new SqlNullValueException();
+
+ ///
+ public static readonly SqlDateTime2 Null = new SqlDateTime2(true);
+
+ ///
+ public bool Equals(SqlDateTime2 other)
+ {
+ return _notNull == other._notNull && _ticks == other._ticks;
+ }
+
+ ///
+ public int CompareTo(SqlDateTime2 other)
+ {
+ var result = _notNull.CompareTo(other._notNull);
+
+ if (result != 0)
+ {
+ return result;
+ }
+
+ return _ticks.CompareTo(other._ticks);
+ }
+
+ ///
+ public int CompareTo(object obj)
+ {
+ if (obj is SqlDateTime2 sqlDateTime2)
+ {
+ return CompareTo(sqlDateTime2);
+ }
+
+ throw ADP.WrongType(obj.GetType(), typeof(SqlDateTime2));
+ }
+
+ ///
+ public static explicit operator SqlDateTime2(DateTime dateTime)
+ {
+ return new SqlDateTime2(dateTime.Ticks);
+ }
+
+ ///
+ public static explicit operator SqlDateTime2(DBNull _)
+ {
+ return Null;
+ }
+ ///
+ public static explicit operator DateTime(SqlDateTime2 sqlDateTime2)
+ {
+ return sqlDateTime2.Value;
+ }
+
+ ///
+ public static XmlQualifiedName GetXsdType(XmlSchemaSet schemaSet)
+ {
+ return new XmlQualifiedName("dateTime2", "http://www.w3.org/2001/XMLSchema");
+ }
+
+ XmlSchema IXmlSerializable.GetSchema()
+ {
+ return null;
+ }
+
+ void IXmlSerializable.ReadXml(XmlReader reader)
+ {
+ string attribute = reader.GetAttribute("nil", "http://www.w3.org/2001/XMLSchema-instance");
+ if (attribute != null && XmlConvert.ToBoolean(attribute))
+ {
+ reader.ReadElementString();
+ _notNull = false;
+ }
+ else
+ {
+ DateTime dateTime = XmlConvert.ToDateTime(reader.ReadElementString(), XmlDateTimeSerializationMode.RoundtripKind);
+ if (dateTime.Kind != DateTimeKind.Unspecified)
+ throw new SqlTypeException(SQLResource.TimeZoneSpecifiedMessage);
+ _ticks = dateTime.Ticks;
+ _notNull = true;
+ }
+ }
+
+ void IXmlSerializable.WriteXml(XmlWriter writer)
+ {
+ if (IsNull)
+ writer.WriteAttributeString("xsi", "nil", "http://www.w3.org/2001/XMLSchema-instance", "true");
+ else
+ writer.WriteString(XmlConvert.ToString(Value, "yyyy-MM-ddTHH:mm:ss.fffffff"));
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj
index c41a99cc4d..c6cb94b7fb 100644
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj
+++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj
@@ -42,6 +42,7 @@
+
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SerializeSqlTypesTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SerializeSqlTypesTest.cs
index eee79bdc63..f57c05033a 100644
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SerializeSqlTypesTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SerializeSqlTypesTest.cs
@@ -9,6 +9,7 @@
using Xunit;
using System.IO;
using System.Runtime.Serialization;
+using Microsoft.Data.SqlTypes;
namespace Microsoft.Data.SqlClient.Tests
{
@@ -27,7 +28,14 @@ private T SerializeAndDeserialize(T value, string expectedSerializedValue)
Assert.Equal(expectedSerializedValue, serializedValue);
memoryStream.Position = 0;
- T deserializedValue = (T)serializer.ReadObject(memoryStream);
+ var obj = serializer.ReadObject(memoryStream);
+ if (obj == null)
+ {
+ // Handle structs that serialize to xsi:nil, which returns null from ReadObject
+ return default;
+ }
+
+ T deserializedValue = (T)obj;
return deserializedValue;
}
}
@@ -80,6 +88,15 @@ public void SerializeAndDeserializeSqlTypes()
SqlString sqlStringDeserialized = SerializeAndDeserialize(sqlString, "abcdefghijklmnopqrstuvwxyz");
// Cannot use StrictEqual because information such as LCID is lost when the SqlString is serialized
Assert.Equal(sqlString.Value, sqlStringDeserialized.Value);
+
+ SqlDateTime2 nullDateTime = SqlDateTime2.Null;
+ Assert.StrictEqual(nullDateTime, SerializeAndDeserialize(nullDateTime, @""));
+
+ SqlDateTime2 minSqlDateTime = new SqlDateTime2(0);
+ Assert.StrictEqual(minSqlDateTime, SerializeAndDeserialize(minSqlDateTime, @"0001-01-01T00:00:00.0000000"));
+
+ SqlDateTime2 maxSqlDateTime = new SqlDateTime2(3155378975999999999);
+ Assert.StrictEqual(maxSqlDateTime, SerializeAndDeserialize(maxSqlDateTime, @"9999-12-31T23:59:59.9999999"));
}
}
}
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlDateTime2Test.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlDateTime2Test.cs
new file mode 100644
index 0000000000..7d2f5cbfe4
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlDateTime2Test.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Microsoft.Data.SqlTypes;
+using Xunit;
+
+namespace Microsoft.Data.SqlClient.Tests
+{
+ public class SqlDateTime2Test
+ {
+ [Fact]
+ public void ConvertFromDBNull()
+ {
+ var value = DBNull.Value;
+
+ SqlDateTime2 sqlDateTime2 = (SqlDateTime2)value;
+ Assert.Equal(SqlDateTime2.Null, sqlDateTime2);
+ }
+
+ [Fact]
+ public void ConvertFromDateTime()
+ {
+ var value = new DateTime(2020, 12, 17, 14, 06, 10, 123, DateTimeKind.Utc);
+
+ SqlDateTime2 expected = new SqlDateTime2(value.Ticks);
+ SqlDateTime2 sqlDateTime2 = (SqlDateTime2)value;
+
+ Assert.Equal(expected, sqlDateTime2);
+ }
+
+ [Fact]
+ public void ConvertToDateTime()
+ {
+ var value = new DateTime(2020, 12, 17, 14, 06, 10, 123, DateTimeKind.Utc);
+
+ SqlDateTime2 expected = new SqlDateTime2(value.Ticks);
+ DateTime converted = (DateTime)expected;
+
+ Assert.Equal(value, converted);
+ }
+
+ [Fact]
+ public void TwoSameShouldBeEquals()
+ {
+ Assert.Equal(SqlDateTime2.Null, SqlDateTime2.Null);
+ }
+
+ [Fact]
+ public void NullShouldBeLessThanSomething()
+ {
+ var result = SqlDateTime2.Null.CompareTo(new SqlDateTime2(1));
+ Assert.Equal(-1, result);
+ }
+
+ [Fact]
+ public void ZeroShouldBeLessThanOne()
+ {
+ var result = new SqlDateTime2(0).CompareTo(new SqlDateTime2(1));
+ Assert.Equal(-1, result);
+ }
+
+ [Fact]
+ public void OutOfLowRangeTicksTest()
+ {
+ Assert.Throws(() => new SqlDateTime2(-1));
+ }
+
+ [Fact]
+ public void OutOfHighRangeTicksTest()
+ {
+ Assert.Throws(() => new SqlDateTime2(long.MaxValue));
+ }
+ [Fact]
+ public void OutOfHighRangeTicksTest2()
+ {
+ Assert.Throws(() => new SqlDateTime2(DateTime.MaxValue.Ticks+1));
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj
index 2a5bf658c5..881c1b3117 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj
@@ -57,6 +57,7 @@
Common\System\Collections\DictionaryExtensions.cs
+
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DateTime2Test/DateTime2Test.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DateTime2Test/DateTime2Test.cs
new file mode 100644
index 0000000000..c3f85ee216
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DateTime2Test/DateTime2Test.cs
@@ -0,0 +1,94 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using Xunit;
+
+namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.DateTime2Test
+{
+ public class DateTime2Test
+ {
+ private static readonly DateTime TestDate = new DateTime(2020, 12, 17, 18, 33, 12, 123).AddTicks(4567);
+
+ private const string SqlDateTimeFormat = "yyyy'-'MM'-'dd HH':'mm':'ss'.'fff";
+ private const string SqlDateTime2Format = "yyyy'-'MM'-'dd HH':'mm':'ss'.'fffffff";
+
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public void NullDefinedAsDateTime2()
+ {
+ var value = GetValueFromReader(@"SELECT CAST(NULL AS DATETIME2)", reader => reader.GetSqlDateTime2(0));
+ Assert.True(value.IsNull);
+ }
+
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public void DateTimeAsNullDateTime2()
+ {
+ var value = GetValueFromReader(@"SELECT CAST(NULL AS DATETIME)", reader => reader.GetSqlDateTime2(0));
+ Assert.True(value.IsNull);
+ }
+
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public void DateAsNullDateTime2()
+ {
+ var value = GetValueFromReader(@"SELECT CAST(NULL AS DATE)", reader => reader.GetSqlDateTime2(0));
+ Assert.True(value.IsNull);
+ }
+
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public void DateTimeAsDateTime2()
+ {
+ var value = GetValueFromReader($"SELECT CAST(CAST('{TestDate.ToString(SqlDateTime2Format)}' AS DATETIME2) as DATETIME)", reader => reader.GetSqlDateTime2(0));
+ Assert.False(value.IsNull);
+ //DATETIME has reduced precision, so we only get the first 3 digits of time
+ var expected = new DateTime(TestDate.Year, TestDate.Month, TestDate.Day, TestDate.Hour, TestDate.Minute, TestDate.Second, 123);
+ Assert.Equal(expected, value.Value);
+ }
+
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public void DateAsDateTime2()
+ {
+ var value = GetValueFromReader($"SELECT CAST(CAST('{TestDate.ToString(SqlDateTimeFormat)}' AS DATETIME) as DATE)", reader => reader.GetSqlDateTime2(0));
+ Assert.False(value.IsNull);
+ Assert.Equal(TestDate.Date, value.Value.Date);
+ }
+
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public void DateTime2AsDateTime2DecreasedPrecision()
+ {
+ var value = GetValueFromReader($"SELECT CAST('{TestDate.ToString(SqlDateTime2Format)}' AS DATETIME2(3))", reader => reader.GetSqlDateTime2(0));
+ Assert.False(value.IsNull);
+ var expected = new DateTime(TestDate.Year, TestDate.Month, TestDate.Day, TestDate.Hour, TestDate.Minute, TestDate.Second, 123);
+ Assert.Equal(expected, value.Value);
+ }
+
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public void DateTime2AsDateTime2FullPrecision()
+ {
+ var value = GetValueFromReader($"SELECT CAST('{TestDate.ToString(SqlDateTime2Format)}' AS DATETIME2(7))", reader => reader.GetSqlDateTime2(0));
+ Assert.False(value.IsNull);
+
+ Assert.Equal(TestDate, value.Value);
+ }
+
+ private static T GetValueFromReader(string sql, Func extractor)
+ {
+ using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString))
+ {
+ connection.Open();
+
+ using (SqlCommand command = connection.CreateCommand())
+ {
+ command.CommandText = sql;
+ command.CommandType = System.Data.CommandType.Text;
+
+ using (var reader = command.ExecuteReader())
+ {
+ reader.Read();
+ return extractor(reader);
+ }
+ }
+ }
+ }
+ }
+}