Skip to content

Commit

Permalink
Support higher-precision timestamps. Fixes #1379
Browse files Browse the repository at this point in the history
  • Loading branch information
bgrainger committed Oct 10, 2023
1 parent 57dfcea commit 021c039
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 17 deletions.
20 changes: 8 additions & 12 deletions src/MySqlConnector/ColumnReaders/TextDateTimeColumnReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ public static object ParseDateTime(ReadOnlySpan<byte> data, bool convertZeroDate
throw new InvalidCastException("Unable to convert MySQL date/time to System.DateTime, set AllowZeroDateTime=True or ConvertZeroDateTime=True in the connection string. See https://mysqlconnector.net/connection-options/");
}

int hour, minute, second, microseconds;
int hour, minute, second, ticks;
if (data.Length == 10)
{
hour = 0;
minute = 0;
second = 0;
microseconds = 0;
ticks = 0;
}
else
{
Expand All @@ -65,28 +65,24 @@ public static object ParseDateTime(ReadOnlySpan<byte> data, bool convertZeroDate

if (data.Length == 19)
{
microseconds = 0;
ticks = 0;
}
else
{
if (data[19] != 46)
goto InvalidDateTime;

if (!Utf8Parser.TryParse(data[20..], out microseconds, out bytesConsumed) || bytesConsumed != data.Length - 20)
if (!Utf8Parser.TryParse(data[20..], out ticks, out bytesConsumed) || bytesConsumed != data.Length - 20 || bytesConsumed > 7)
goto InvalidDateTime;
for (; bytesConsumed < 6; bytesConsumed++)
microseconds *= 10;
for (; bytesConsumed < 7; bytesConsumed++)
ticks *= 10;
}
}

try
{
return allowZeroDateTime ? (object) new MySqlDateTime(year, month, day, hour, minute, second, microseconds) :
#if NET7_0_OR_GREATER
new DateTime(year, month, day, hour, minute, second, microseconds / 1000, microseconds % 1000, dateTimeKind);
#else
new DateTime(year, month, day, hour, minute, second, microseconds / 1000, dateTimeKind).AddTicks(microseconds % 1000 * 10);
#endif
return allowZeroDateTime ? (ticks % 10 == 0 ? (object) new MySqlDateTime(year, month, day, hour, minute, second, ticks / 10) : throw new NotSupportedException("MySqlDateTime does not support sub-microsecond precision")) :
new DateTime(year, month, day, hour, minute, second, dateTimeKind).AddTicks(ticks);
}
catch (Exception ex)
{
Expand Down
31 changes: 26 additions & 5 deletions tests/MySqlConnector.Tests/ColumnReaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,41 @@ public class ColumnReaderTests
[InlineData("2345-12-31 12:34:56.1234", "2345-12-31T12:34:56.1234000")]
[InlineData("2345-12-31 12:34:56.12345", "2345-12-31T12:34:56.1234500")]
[InlineData("2345-12-31 12:34:56.123456", "2345-12-31T12:34:56.1234560")]
[InlineData("2345-12-31 12:34:56.1234567", "2345-12-31T12:34:56.1234567")]
[InlineData("2345-12-31 12:34:56.01", "2345-12-31T12:34:56.0100000")]
[InlineData("2345-12-31 12:34:56.001", "2345-12-31T12:34:56.0010000")]
[InlineData("2345-12-31 12:34:56.0001", "2345-12-31T12:34:56.0001000")]
[InlineData("2345-12-31 12:34:56.00001", "2345-12-31T12:34:56.0000100")]
[InlineData("2345-12-31 12:34:56.000001", "2345-12-31T12:34:56.0000010")]
[InlineData("2345-12-31 12:34:56.0000001", "2345-12-31T12:34:56.0000001")]
[InlineData("9999-12-31 23:59:59.999999", "9999-12-31T23:59:59.9999990")]
[InlineData("9999-12-31 23:59:59.9999999", "9999-12-31T23:59:59.9999999")]
public void ParseDateTime(string input, string expected)
{
var dateTime = (DateTime) TextDateTimeColumnReader.ParseDateTime(Encoding.UTF8.GetBytes(input), convertZeroDateTime: false, allowZeroDateTime: false, dateTimeKind: DateTimeKind.Unspecified);
Assert.Equal(expected, dateTime.ToString("O"));
var actual = (DateTime) TextDateTimeColumnReader.ParseDateTime(Encoding.UTF8.GetBytes(input), convertZeroDateTime: false, allowZeroDateTime: false, dateTimeKind: DateTimeKind.Unspecified);
Assert.Equal(expected, actual.ToString("O"));
}

var mySqlDateTime = (MySqlDateTime) TextDateTimeColumnReader.ParseDateTime(Encoding.UTF8.GetBytes(input), convertZeroDateTime: false, allowZeroDateTime: true, dateTimeKind: DateTimeKind.Unspecified);
Assert.Equal(expected, mySqlDateTime.GetDateTime().ToString("O"));
[Theory]
[InlineData("0001-01-01", "0001-01-01T00:00:00.0000000")]
[InlineData("2345-12-31", "2345-12-31T00:00:00.0000000")]
[InlineData("2345-12-31 12:34:56", "2345-12-31T12:34:56.0000000")]
[InlineData("2345-12-31 12:34:56.1", "2345-12-31T12:34:56.1000000")]
[InlineData("2345-12-31 12:34:56.12", "2345-12-31T12:34:56.1200000")]
[InlineData("2345-12-31 12:34:56.123", "2345-12-31T12:34:56.1230000")]
[InlineData("2345-12-31 12:34:56.1234", "2345-12-31T12:34:56.1234000")]
[InlineData("2345-12-31 12:34:56.12345", "2345-12-31T12:34:56.1234500")]
[InlineData("2345-12-31 12:34:56.123456", "2345-12-31T12:34:56.1234560")]
[InlineData("2345-12-31 12:34:56.01", "2345-12-31T12:34:56.0100000")]
[InlineData("2345-12-31 12:34:56.001", "2345-12-31T12:34:56.0010000")]
[InlineData("2345-12-31 12:34:56.0001", "2345-12-31T12:34:56.0001000")]
[InlineData("2345-12-31 12:34:56.00001", "2345-12-31T12:34:56.0000100")]
[InlineData("2345-12-31 12:34:56.000001", "2345-12-31T12:34:56.0000010")]
[InlineData("9999-12-31 23:59:59.999999", "9999-12-31T23:59:59.9999990")]
public void ParseMySqlDateTime(string input, string expected)
{
var actual = (MySqlDateTime) TextDateTimeColumnReader.ParseDateTime(Encoding.UTF8.GetBytes(input), convertZeroDateTime: false, allowZeroDateTime: true, dateTimeKind: DateTimeKind.Unspecified);
Assert.Equal(expected, actual.GetDateTime().ToString("O"));
}

[Fact]
Expand Down Expand Up @@ -61,7 +83,6 @@ public void AllowZeroDateTime()
[InlineData("2000-01-01 01:01:60")]
[InlineData("2000-01-01T01:01:01")]
[InlineData("2000-01-01 01-01-01")]
[InlineData("2000-01-01 01:01:01.1234567")]
[InlineData("2000-01-01 01:01:01.12345678")]
[InlineData("2000-01-01 01:01:01.123456789")]
public void InvalidDateTime(string input)
Expand Down

0 comments on commit 021c039

Please sign in to comment.