Skip to content

Commit

Permalink
Implementation of DateTime2 + accompanying GetSqlDateTime2 method (#846)
Browse files Browse the repository at this point in the history
  • Loading branch information
bjornbouetsmith committed Dec 18, 2020
1 parent 699ae37 commit b0b5515
Show file tree
Hide file tree
Showing 16 changed files with 513 additions and 2 deletions.
19 changes: 19 additions & 0 deletions doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,25 @@
]]></format>
</remarks>
</GetSqlDateTime>
<GetSqlDateTime2>
<param name="i">The zero-based column ordinal.</param>
<summary>
Gets the value of the specified column as a <see cref="T:System.Data.SqlTypes.SqlDateTime2" />.
</summary>
<returns>
The value of the column expressed as a <see cref="T:System.Data.SqlTypes.SqlDateTime2" />.
</returns>
<remarks>
<format type="text/markdown">
<![CDATA[
## Remarks
No conversions are performed; therefore, the data retrieved must already be a date/time value or Null, otherwise an exception is thrown.
]]>
</format>
</remarks>
</GetSqlDateTime2>
<GetSqlDecimal>
<param name="i">The zero-based column ordinal.</param>
<summary>Gets the value of the specified column as a <see cref="T:System.Data.SqlTypes.SqlDecimal" />.</summary>
Expand Down
72 changes: 72 additions & 0 deletions doc/snippets/Microsoft.Data.SqlTypes/SqlDateTime2.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<docs>
<members name="SqlDateTime2">
<SqlFileStream>
<summary>Exposes SQL Server data that is stored as a DATETIME2.</summary>
<remarks>
<format type="text/markdown">
<![CDATA[
## Remarks
The <xref:Microsoft.Data.SqlTypes.SqlDateTime2> class is used to work with `DATETIME2` data.
`DATETIME2`has a wider range than DATETIME and thus needs its own type.
]]>
</format>
</remarks>
</SqlFileStream>
<ctor1>
<param name="isNull">Whether or not this instance should be considered null - true means null, false not null</param>
<summary>
Initializes a new instance of the <see cref="T:Microsoft.Data.SqlTypes.SqlDateTime2" /> struct.
Will initialize the datetime value to 0.
</summary>
</ctor1>
<ctor2>
<param name="ticks">Number of ticks to initialize this instance with - in the range of <see cref="T:Microsoft.DateTime.MinValue.Ticks"/> &amp; <see cref="T:Microsoft.DateTime.MaxValue.Ticks"/></param>

<summary>
Initializes a new instance of the <see cref="T:Microsoft.Data.SqlTypes.SqlDateTime2" /> class.
</summary>
<exception cref="T:System.ArgumentOutOfRangeException">
<paramref name="ticks" /> is not in the range of <see cref="T:Microsoft.DateTime.MinValue.Ticks"/> &amp; <see cref="T:Microsoft.DateTime.MaxValue.Ticks"/>
</exception>
</ctor2>
<Value>
<summary>
Gets the <see cref="T:System.DateTime"/> representation of this instance.
</summary>
<exception cref="T:Microsoft.Data.SqlTypes.SqlNullValueException">
If this instance is null
</exception>
</Value>
<Null>
<summary>
Gets an instance representing the value NULL from the database.
</summary>
</Null>
<OperatorDateTime>
<summary>
Converts a DateTime into a SqlDateTime2
</summary>
</OperatorDateTime>
<OperatorDBNull>
<summary>
Converts a DBNull instance into a Null SqlDateTime2
</summary>
</OperatorDBNull>
<OperatorSqlDateTime>
<summary>
Converts a SqlDateTime2 into DateTime
</summary>
<exception cref="T:Microsoft.Data.SqlTypes.SqlNullValueException">
If the SqlDateTime2 instance has a null value
</exception>
</OperatorSqlDateTime>
<GetXsdType>
<summary>
returns a <see cref="T:System.Xml.XmlQualifiedName"/> for serialization purposes
</summary>
<param name="schemaSet">unused parameter</param>
</GetXsdType>
</members>
</docs>
1 change: 1 addition & 0 deletions src/Microsoft.Data.SqlClient.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,7 @@
<Compile Include="Microsoft\Data\SqlClient\SqlColumnEncryptionEnclaveProvider.cs" />
<Compile Include="Microsoft\Data\SqlClient\SqlEnclaveAttestationParameters.cs" />
<Compile Include="Microsoft\Data\SqlClient\EnclaveDelegate.cs" />
<Compile Include="..\..\src\Microsoft\Data\SqlTypes\SqlDateTime2.cs" Link="Microsoft\Data\SQLTypes\SqlDateTime2.cs" />
</ItemGroup>
<!-- Windows only -->
<ItemGroup Condition="'$(TargetsWindows)' == 'true'">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2397,6 +2397,13 @@ virtual public SqlDateTime GetSqlDateTime(int i)
return _data[i].SqlDateTime;
}

/// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml' path='docs/members[@name="SqlDataReader"]/GetSqlDateTime2/*' />
virtual public SqlDateTime2 GetSqlDateTime2(int i)
{
ReadColumn(i);
return _data[i].SqlDateTime2;
}

/// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml' path='docs/members[@name="SqlDataReader"]/GetSqlDecimal/*' />
virtual public SqlDecimal GetSqlDecimal(int i)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@
<Compile Include="..\..\src\Microsoft\Data\Common\ActivityCorrelator.cs">
<Link>Microsoft\Data\Common\ActivityCorrelator.cs</Link>
</Compile>
<Compile Include="..\..\src\Microsoft\Data\SqlTypes\SqlDateTime2.cs">
<Link>Microsoft\Data\SqlTypes\SqlDateTime2.cs</Link>
</Compile>
<Compile Include="..\..\src\Microsoft\Data\Sql\SqlNotificationRequest.cs">
<Link>Microsoft\Data\Sql\SqlNotificationRequest.cs</Link>
</Compile>
Expand Down Expand Up @@ -535,4 +538,4 @@
<Import Project="$(NetFxSource)tools\targets\GenerateThisAssemblyCs.targets" />
<Import Project="$(NetFxSource)tools\targets\GenerateAssemblyRef.targets" />
<Import Project="$(NetFxSource)tools\targets\GenerateAssemblyInfo.targets" />
</Project>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2777,6 +2777,13 @@ virtual public SqlDateTime GetSqlDateTime(int i)
return _data[i].SqlDateTime;
}

/// <include file='..\..\..\..\..\..\..\doc\snippets\Microsoft.Data.SqlClient\SqlDataReader.xml' path='docs/members[@name="SqlDataReader"]/GetSqlDateTime2/*' />
virtual public SqlDateTime2 GetSqlDateTime2(int i)
{
ReadColumn(i);
return _data[i].SqlDateTime2;
}

/// <include file='..\..\..\..\..\..\..\doc\snippets\Microsoft.Data.SqlClient\SqlDataReader.xml' path='docs/members[@name="SqlDataReader"]/GetSqlDecimal/*' />
virtual public SqlDecimal GetSqlDecimal(int i)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
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
{
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlTypes/SqlDateTime2.xml' path='docs/members[@name="SqlDateTime2"]/SqlDateTime2/*' />
[XmlSchemaProvider("GetXsdType")]
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct SqlDateTime2 : INullable, IComparable<SqlDateTime2>, IEquatable<SqlDateTime2>, 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.Tics


/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlTypes/SqlDateTime2.xml' path='docs/members[@name="SqlDateTime2"]/ctor1/*' />
private SqlDateTime2(bool isNull)
{
_notNull = !isNull;
_ticks = 0;
}

/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlTypes/SqlDateTime2.xml' path='docs/members[@name="SqlDateTime2"]/ctor2/*' />
public SqlDateTime2(long ticks)
{
if (ticks < _minTicks || ticks > _maxTicks)
{
throw ADP.ArgumentOutOfRange(nameof(ticks));
}
_notNull = true;
_ticks = ticks;

}

/// <inheritdoc />
public bool IsNull => !_notNull;

/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlTypes/SqlDateTime2.xml' path='docs/members[@name="SqlDateTime2"]/Value/*' />
public DateTime Value => _notNull ? new DateTime(_ticks) : throw new SqlNullValueException();

/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlTypes/SqlDateTime2.xml' path='docs/members[@name="SqlDateTime2"]/Null/*' />
public static readonly SqlDateTime2 Null = new SqlDateTime2(true);

/// <inheritdoc />
public bool Equals(SqlDateTime2 other)
{
return _notNull == other._notNull && _ticks == other._ticks;
}

/// <inheritdoc />
public int CompareTo(SqlDateTime2 other)
{
var result = _notNull.CompareTo(other._notNull);

if (result != 0)
{
return result;
}

return _ticks.CompareTo(other._ticks);
}

/// <inheritdoc />
public int CompareTo(object obj)
{
if (obj is SqlDateTime2 sqlDateTime2)
{
return CompareTo(sqlDateTime2);
}

throw ADP.WrongType(obj.GetType(), typeof(SqlDateTime2));
}

/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlTypes/SqlDateTime2.xml' path='docs/members[@name="SqlDateTime2"]/OperatorDateTime/*' />
public static explicit operator SqlDateTime2(DateTime dateTime)
{
return new SqlDateTime2(dateTime.Ticks);
}

/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlTypes/SqlDateTime2.xml' path='docs/members[@name="SqlDateTime2"]/OperatorDBNull/*' />
public static explicit operator SqlDateTime2(DBNull _)
{
return Null;
}
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlTypes/SqlDateTime2.xml' path='docs/members[@name="SqlDateTime2"]/OperatorSqlDateTime/*' />
public static explicit operator DateTime(SqlDateTime2 sqlDateTime2)
{
return sqlDateTime2.Value;
}

/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlTypes/SqlDateTime2.xml' path='docs/members[@name="SqlDateTime2"]/GetXsdType/*' />
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"));
}
}
}
Loading

0 comments on commit b0b5515

Please sign in to comment.