Skip to content

Commit 9c450ed

Browse files
authored
Merge pull request #479 from ejball/datetimekind
Support DateTimeKind connection setting.
2 parents 9636fc5 + 64496ac commit 9c450ed

File tree

12 files changed

+144
-43
lines changed

12 files changed

+144
-43
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: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,18 +43,19 @@ public ConnectionSettings(MySqlConnectionStringBuilder csb)
4343
ConnectionLifeTime = Math.Min(csb.ConnectionLifeTime, uint.MaxValue / 1000) * 1000;
4444
ConnectionReset = csb.ConnectionReset;
4545
ConnectionIdlePingTime = Math.Min(csb.ConnectionIdlePingTime, uint.MaxValue / 1000) * 1000;
46-
ConnectionIdleTimeout = (int)csb.ConnectionIdleTimeout;
46+
ConnectionIdleTimeout = (int) csb.ConnectionIdleTimeout;
4747
if (csb.MinimumPoolSize > csb.MaximumPoolSize)
4848
throw new MySqlException("MaximumPoolSize must be greater than or equal to MinimumPoolSize");
49-
MinimumPoolSize = (int)csb.MinimumPoolSize;
50-
MaximumPoolSize = (int)csb.MaximumPoolSize;
49+
MinimumPoolSize = (int) csb.MinimumPoolSize;
50+
MaximumPoolSize = (int) csb.MaximumPoolSize;
5151

5252
// Other Options
5353
AllowPublicKeyRetrieval = csb.AllowPublicKeyRetrieval;
5454
AllowUserVariables = csb.AllowUserVariables;
5555
AutoEnlist = csb.AutoEnlist;
56-
ConnectionTimeout = (int)csb.ConnectionTimeout;
56+
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
{

tests/MySqlConnector.Tests/MySqlConnectionStringBuilderTests.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ public void Defaults()
2626
#endif
2727
Assert.Equal(15u, csb.ConnectionTimeout);
2828
Assert.False(csb.ConvertZeroDateTime);
29+
#if !BASELINE
30+
Assert.Equal(MySqlDateTimeKind.Unspecified, csb.DateTimeKind);
31+
#endif
2932
Assert.Equal("", csb.Database);
3033
Assert.Equal(30u, csb.DefaultCommandTimeout);
3134
#if !BASELINE
@@ -76,6 +79,9 @@ public void ParseConnectionString()
7679
"connection lifetime=15;" +
7780
"ConnectionReset=false;" +
7881
"Convert Zero Datetime=true;" +
82+
#if !BASELINE
83+
"datetimekind=utc;" +
84+
#endif
7985
"default command timeout=123;" +
8086
#if !BASELINE
8187
"connection idle ping time=60;" +
@@ -109,6 +115,9 @@ public void ParseConnectionString()
109115
Assert.False(csb.ConnectionReset);
110116
Assert.Equal(30u, csb.ConnectionTimeout);
111117
Assert.True(csb.ConvertZeroDateTime);
118+
#if !BASELINE
119+
Assert.Equal(MySqlDateTimeKind.Utc, csb.DateTimeKind);
120+
#endif
112121
Assert.Equal("schema_name", csb.Database);
113122
Assert.Equal(123u, csb.DefaultCommandTimeout);
114123
#if !BASELINE

0 commit comments

Comments
 (0)