From 021c0395ebb3e1c6d0d74e652ca7453bcd016e7e Mon Sep 17 00:00:00 2001 From: Bradley Grainger Date: Tue, 10 Oct 2023 14:57:26 -0700 Subject: [PATCH] Support higher-precision timestamps. Fixes #1379 --- .../ColumnReaders/TextDateTimeColumnReader.cs | 20 +++++------- .../MySqlConnector.Tests/ColumnReaderTests.cs | 31 ++++++++++++++++--- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/src/MySqlConnector/ColumnReaders/TextDateTimeColumnReader.cs b/src/MySqlConnector/ColumnReaders/TextDateTimeColumnReader.cs index 1064fde65..b2863e9db 100644 --- a/src/MySqlConnector/ColumnReaders/TextDateTimeColumnReader.cs +++ b/src/MySqlConnector/ColumnReaders/TextDateTimeColumnReader.cs @@ -40,13 +40,13 @@ public static object ParseDateTime(ReadOnlySpan 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 { @@ -65,28 +65,24 @@ public static object ParseDateTime(ReadOnlySpan 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) { diff --git a/tests/MySqlConnector.Tests/ColumnReaderTests.cs b/tests/MySqlConnector.Tests/ColumnReaderTests.cs index 999d08206..88ba92d51 100644 --- a/tests/MySqlConnector.Tests/ColumnReaderTests.cs +++ b/tests/MySqlConnector.Tests/ColumnReaderTests.cs @@ -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] @@ -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)