Skip to content

Commit 64496ac

Browse files
committed
Support DateTimeKind connection option.
Fixes #477.
1 parent 7863506 commit 64496ac

File tree

12 files changed

+118
-17
lines changed

12 files changed

+118
-17
lines changed

docs/content/connection-options.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,13 @@ These are the other options that MySqlConnector supports. They are set to sensib
189189
<td>false</td>
190190
<td>True to have MySqlDataReader.GetValue() and MySqlDataReader.GetDateTime() return DateTime.MinValue for date or datetime columns that have disallowed values.</td>
191191
</tr>
192+
<tr>
193+
<td>DateTimeKind</td>
194+
<td>Unspecified</td>
195+
<td>The <code>DateTimeKind</code> used when <code>MySqlDataReader</code> returns a <code>DateTime</code>. If set to <code>Utc</code> or <code>Local</code>,
196+
a <code>MySqlException</code> will be thrown if a <code>DateTime</code> command parameter has a <code>Kind</code> of <code>Local</code> or <code>Utc</code>,
197+
respectively.</td>
198+
</tr>
192199
<tr>
193200
<td>Default Command Timeout, Command Timeout, DefaultCommandTimeout</td>
194201
<td>30</td>

src/MySqlConnector/Core/ConnectionSettings.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ public ConnectionSettings(MySqlConnectionStringBuilder csb)
5555
AutoEnlist = csb.AutoEnlist;
5656
ConnectionTimeout = (int) csb.ConnectionTimeout;
5757
ConvertZeroDateTime = csb.ConvertZeroDateTime;
58+
DateTimeKind = (DateTimeKind) csb.DateTimeKind;
5859
DefaultCommandTimeout = (int) csb.DefaultCommandTimeout;
5960
ForceSynchronous = csb.ForceSynchronous;
6061
IgnoreCommandTransaction = csb.IgnoreCommandTransaction;
@@ -105,6 +106,7 @@ public ConnectionSettings(MySqlConnectionStringBuilder csb)
105106
public bool AutoEnlist { get; }
106107
public int ConnectionTimeout { get; }
107108
public bool ConvertZeroDateTime { get; }
109+
public DateTimeKind DateTimeKind { get; }
108110
public int DefaultCommandTimeout { get; }
109111
public bool ForceSynchronous { get; }
110112
public bool IgnoreCommandTransaction { get; }

src/MySqlConnector/Core/Row.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,7 @@ private DateTime ParseDateTime(ArraySegment<byte> value)
456456
return new DateTime(year, month, day, hour, minute, second);
457457

458458
var microseconds = int.Parse(parts[6] + new string('0', 6 - parts[6].Length), CultureInfo.InvariantCulture);
459-
return new DateTime(year, month, day, hour, minute, second, microseconds / 1000).AddTicks(microseconds % 1000 * 10);
459+
return new DateTime(year, month, day, hour, minute, second, microseconds / 1000, Connection.DateTimeKind).AddTicks(microseconds % 1000 * 10);
460460
}
461461

462462
private static TimeSpan ParseTimeSpan(ArraySegment<byte> value)

src/MySqlConnector/Core/StatementPreparer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ private void DoAppendParameter(int parameterIndex, int textIndex, int textLength
7373
var parameter = m_preparer.m_parameters[parameterIndex];
7474
if (parameter.Direction != ParameterDirection.Input && (m_preparer.m_options & StatementPreparerOptions.AllowOutputParameters) == 0)
7575
throw new MySqlException("Only ParameterDirection.Input is supported when CommandType is Text (parameter name: {0})".FormatInvariant(parameter.ParameterName));
76-
m_preparer.m_parameters[parameterIndex].AppendSqlString(m_writer, m_preparer.m_options);
76+
m_preparer.m_parameters[parameterIndex].AppendSqlString(m_writer, m_preparer.m_options, parameter.ParameterName);
7777
m_lastIndex = textIndex + textLength;
7878
}
7979

src/MySqlConnector/Core/StatementPreparerOptions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,7 @@ internal enum StatementPreparerOptions
99
AllowUserVariables = 1,
1010
OldGuids = 2,
1111
AllowOutputParameters = 4,
12+
DateTimeUtc = 8,
13+
DateTimeLocal = 16,
1214
}
1315
}

src/MySqlConnector/Core/TextCommandExecutor.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ private PayloadData CreateQueryPayload(string commandText, MySqlParameterCollect
9494
statementPreparerOptions |= StatementPreparerOptions.AllowUserVariables;
9595
if (m_command.Connection.OldGuids)
9696
statementPreparerOptions |= StatementPreparerOptions.OldGuids;
97+
if (m_command.Connection.DateTimeKind == DateTimeKind.Utc)
98+
statementPreparerOptions |= StatementPreparerOptions.DateTimeUtc;
99+
else if (m_command.Connection.DateTimeKind == DateTimeKind.Local)
100+
statementPreparerOptions |= StatementPreparerOptions.DateTimeLocal;
97101
if (m_command.CommandType == CommandType.StoredProcedure)
98102
statementPreparerOptions |= StatementPreparerOptions.AllowOutputParameters;
99103
var preparer = new StatementPreparer(commandText, parameterCollection, statementPreparerOptions);

src/MySqlConnector/MySql.Data.MySqlClient/MySqlConnection.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,7 @@ internal async Task<CachedProcedure> GetCachedProcedure(IOBehavior ioBehavior, s
348348
internal MySqlTransaction CurrentTransaction { get; set; }
349349
internal bool AllowUserVariables => m_connectionSettings.AllowUserVariables;
350350
internal bool ConvertZeroDateTime => m_connectionSettings.ConvertZeroDateTime;
351+
internal DateTimeKind DateTimeKind => m_connectionSettings.DateTimeKind;
351352
internal int DefaultCommandTimeout => GetConnectionSettings().DefaultCommandTimeout;
352353
internal bool IgnoreCommandTransaction => m_connectionSettings.IgnoreCommandTransaction;
353354
internal bool OldGuids => m_connectionSettings.OldGuids;

src/MySqlConnector/MySql.Data.MySqlClient/MySqlConnectionStringBuilder.cs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,12 @@ public bool ConvertZeroDateTime
159159
set => MySqlConnectionStringOption.ConvertZeroDateTime.SetValue(this, value);
160160
}
161161

162+
public MySqlDateTimeKind DateTimeKind
163+
{
164+
get => MySqlConnectionStringOption.DateTimeKind.GetValue(this);
165+
set => MySqlConnectionStringOption.DateTimeKind.SetValue(this, value);
166+
}
167+
162168
public uint DefaultCommandTimeout
163169
{
164170
get => MySqlConnectionStringOption.DefaultCommandTimeout.GetValue(this);
@@ -294,6 +300,7 @@ internal abstract class MySqlConnectionStringOption
294300
public static readonly MySqlConnectionStringOption<string> CharacterSet;
295301
public static readonly MySqlConnectionStringOption<uint> ConnectionTimeout;
296302
public static readonly MySqlConnectionStringOption<bool> ConvertZeroDateTime;
303+
public static readonly MySqlConnectionStringOption<MySqlDateTimeKind> DateTimeKind;
297304
public static readonly MySqlConnectionStringOption<uint> DefaultCommandTimeout;
298305
public static readonly MySqlConnectionStringOption<bool> ForceSynchronous;
299306
public static readonly MySqlConnectionStringOption<bool> IgnoreCommandTransaction;
@@ -427,6 +434,10 @@ static MySqlConnectionStringOption()
427434
keys: new[] { "Convert Zero Datetime", "ConvertZeroDateTime" },
428435
defaultValue: false));
429436

437+
AddOption(DateTimeKind = new MySqlConnectionStringOption<MySqlDateTimeKind>(
438+
keys: new[] { "DateTimeKind" },
439+
defaultValue: MySqlDateTimeKind.Unspecified));
440+
430441
AddOption(DefaultCommandTimeout = new MySqlConnectionStringOption<uint>(
431442
keys: new[] { "Default Command Timeout", "DefaultCommandTimeout", "Command Timeout" },
432443
defaultValue: 30u));
@@ -505,21 +516,11 @@ private static T ChangeType(object objectValue)
505516
return (T) (object) false;
506517
}
507518

508-
if (typeof(T) == typeof(MySqlLoadBalance) && objectValue is string loadBalanceString)
509-
{
510-
foreach (var val in Enum.GetValues(typeof(T)))
511-
{
512-
if (string.Equals(loadBalanceString, val.ToString(), StringComparison.OrdinalIgnoreCase))
513-
return (T) val;
514-
}
515-
throw new InvalidOperationException("Value '{0}' not supported for option '{1}'.".FormatInvariant(objectValue, typeof(T).Name));
516-
}
517-
518-
if (typeof(T) == typeof(MySqlSslMode) && objectValue is string sslModeString)
519+
if ((typeof(T) == typeof(MySqlLoadBalance) || typeof(T) == typeof(MySqlSslMode) || typeof(T) == typeof(MySqlDateTimeKind)) && objectValue is string enumString)
519520
{
520521
foreach (var val in Enum.GetValues(typeof(T)))
521522
{
522-
if (string.Equals(sslModeString, val.ToString(), StringComparison.OrdinalIgnoreCase))
523+
if (string.Equals(enumString, val.ToString(), StringComparison.OrdinalIgnoreCase))
523524
return (T) val;
524525
}
525526
throw new InvalidOperationException("Value '{0}' not supported for option '{1}'.".FormatInvariant(objectValue, typeof(T).Name));
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System;
2+
3+
namespace MySql.Data.MySqlClient
4+
{
5+
/// <summary>
6+
/// The <see cref="DateTimeKind" /> used when reading <see cref="DateTime" /> from the databsae.
7+
/// </summary>
8+
public enum MySqlDateTimeKind
9+
{
10+
/// <summary>
11+
/// Use <see cref="DateTimeKind.Unspecified" /> when reading; allow any <see cref="DateTimeKind" /> in command parameters.
12+
/// </summary>
13+
Unspecified = DateTimeKind.Unspecified,
14+
15+
/// <summary>
16+
/// Use <see cref="DateTimeKind.Utc" /> when reading; reject <see cref="DateTimeKind.Local" /> in command parameters.
17+
/// </summary>
18+
Utc = DateTimeKind.Utc,
19+
20+
/// <summary>
21+
/// Use <see cref="DateTimeKind.Local" /> when reading; reject <see cref="DateTimeKind.Utc" /> in command parameters.
22+
/// </summary>
23+
Local = DateTimeKind.Local,
24+
}
25+
}

src/MySqlConnector/MySql.Data.MySqlClient/MySqlParameter.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ private MySqlParameter(MySqlParameter other, string parameterName)
173173

174174
internal string NormalizedParameterName { get; private set; }
175175

176-
internal void AppendSqlString(BinaryWriter writer, StatementPreparerOptions options)
176+
internal void AppendSqlString(BinaryWriter writer, StatementPreparerOptions options, string parameterName)
177177
{
178178
if (Value == null || Value == DBNull.Value)
179179
{
@@ -236,9 +236,14 @@ internal void AppendSqlString(BinaryWriter writer, StatementPreparerOptions opti
236236
{
237237
writer.WriteUtf8("{0:R}".FormatInvariant(Value));
238238
}
239-
else if (Value is DateTime)
239+
else if (Value is DateTime dateTimeValue)
240240
{
241-
writer.WriteUtf8("timestamp('{0:yyyy'-'MM'-'dd' 'HH':'mm':'ss'.'ffffff}')".FormatInvariant(Value));
241+
if ((options & StatementPreparerOptions.DateTimeUtc) != 0 && dateTimeValue.Kind == DateTimeKind.Local)
242+
throw new MySqlException("DateTime.Kind must not be Local when DateTimeKind setting is Utc (parameter name: {0})".FormatInvariant(parameterName));
243+
else if ((options & StatementPreparerOptions.DateTimeLocal) != 0 && dateTimeValue.Kind == DateTimeKind.Utc)
244+
throw new MySqlException("DateTime.Kind must not be Utc when DateTimeKind setting is Local (parameter name: {0})".FormatInvariant(parameterName));
245+
246+
writer.WriteUtf8("timestamp('{0:yyyy'-'MM'-'dd' 'HH':'mm':'ss'.'ffffff}')".FormatInvariant(dateTimeValue));
242247
}
243248
else if (Value is DateTimeOffset dateTimeOffsetValue)
244249
{

0 commit comments

Comments
 (0)