Skip to content

Commit 8e1e56a

Browse files
committed
Merge fix for negative time parsing.
2 parents 530b011 + b1b2357 commit 8e1e56a

File tree

4 files changed

+124
-43
lines changed

4 files changed

+124
-43
lines changed

src/MySqlConnector/Core/Row.cs

Lines changed: 1 addition & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ public object GetValue(int ordinal)
406406
return ParseDateTime(data);
407407

408408
case ColumnType.Time:
409-
return ParseTimeSpan(data);
409+
return Utility.ParseTimeSpan(data);
410410

411411
case ColumnType.Year:
412412
return ParseInt32(data);
@@ -528,45 +528,6 @@ private object ParseDateTime(ReadOnlySpan<byte> value)
528528
throw new FormatException("Couldn't interpret '{0}' as a valid DateTime".FormatInvariant(Encoding.UTF8.GetString(value)));
529529
}
530530

531-
private static TimeSpan ParseTimeSpan(ReadOnlySpan<byte> value)
532-
{
533-
var originalValue = value;
534-
if (!Utf8Parser.TryParse(value, out int hours, out var bytesConsumed))
535-
goto InvalidTimeSpan;
536-
if (value.Length == bytesConsumed || value[bytesConsumed] != 58)
537-
goto InvalidTimeSpan;
538-
value = value.Slice(bytesConsumed + 1);
539-
540-
if (!Utf8Parser.TryParse(value, out int minutes, out bytesConsumed) || bytesConsumed != 2)
541-
goto InvalidTimeSpan;
542-
if (value.Length < 3 || value[2] != 58)
543-
goto InvalidTimeSpan;
544-
value = value.Slice(3);
545-
if (hours < 0)
546-
minutes = -minutes;
547-
548-
if (!Utf8Parser.TryParse(value, out int seconds, out bytesConsumed) || bytesConsumed != 2)
549-
goto InvalidTimeSpan;
550-
if (hours < 0)
551-
seconds = -seconds;
552-
if (value.Length == 2)
553-
return new TimeSpan(hours, minutes, seconds);
554-
555-
if (value[2] != 46)
556-
goto InvalidTimeSpan;
557-
value = value.Slice(3);
558-
if (!Utf8Parser.TryParse(value, out int microseconds, out bytesConsumed) || bytesConsumed != value.Length)
559-
goto InvalidTimeSpan;
560-
for (; bytesConsumed < 6; bytesConsumed++)
561-
microseconds *= 10;
562-
if (hours < 0)
563-
microseconds = -microseconds;
564-
return new TimeSpan(0, hours, minutes, seconds, microseconds / 1000) + TimeSpan.FromTicks(microseconds % 1000 * 10);
565-
566-
InvalidTimeSpan:
567-
throw new FormatException("Couldn't interpret '{0}' as a valid TimeSpan".FormatInvariant(Encoding.UTF8.GetString(originalValue)));
568-
}
569-
570531
private static Guid CreateGuidFromBytes(MySqlGuidFormat guidFormat, ReadOnlySpan<byte> bytes)
571532
{
572533
#if NET45 || NET461 || NETSTANDARD1_3 || NETSTANDARD2_0

src/MySqlConnector/Utilities/Utility.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Buffers.Text;
23
using System.Globalization;
34
using System.IO;
45
using System.Linq;
@@ -229,6 +230,65 @@ public static int FindNextIndex(ReadOnlySpan<byte> data, int offset, ReadOnlySpa
229230
return -1;
230231
}
231232

233+
public static TimeSpan ParseTimeSpan(ReadOnlySpan<byte> value)
234+
{
235+
var originalValue = value;
236+
237+
// parse (optional) leading minus sign
238+
var isNegative = false;
239+
if (value.Length > 0 && value[0] == 0x2D)
240+
{
241+
isNegative = true;
242+
value = value.Slice(1);
243+
}
244+
245+
// parse hours (0-838)
246+
if (!Utf8Parser.TryParse(value, out int hours, out var bytesConsumed) || hours < 0 || hours > 838)
247+
goto InvalidTimeSpan;
248+
if (value.Length == bytesConsumed || value[bytesConsumed] != 58)
249+
goto InvalidTimeSpan;
250+
value = value.Slice(bytesConsumed + 1);
251+
252+
// parse minutes (0-59)
253+
if (!Utf8Parser.TryParse(value, out int minutes, out bytesConsumed) || bytesConsumed != 2 || minutes < 0 || minutes > 59)
254+
goto InvalidTimeSpan;
255+
if (value.Length < 3 || value[2] != 58)
256+
goto InvalidTimeSpan;
257+
value = value.Slice(3);
258+
259+
// parse seconds (0-59)
260+
if (!Utf8Parser.TryParse(value, out int seconds, out bytesConsumed) || bytesConsumed != 2 || seconds < 0 || seconds > 59)
261+
goto InvalidTimeSpan;
262+
263+
int microseconds;
264+
if (value.Length == 2)
265+
{
266+
microseconds = 0;
267+
}
268+
else
269+
{
270+
if (value[2] != 46)
271+
goto InvalidTimeSpan;
272+
value = value.Slice(3);
273+
if (!Utf8Parser.TryParse(value, out microseconds, out bytesConsumed) || bytesConsumed != value.Length || microseconds < 0 || microseconds > 999_999)
274+
goto InvalidTimeSpan;
275+
for (; bytesConsumed < 6; bytesConsumed++)
276+
microseconds *= 10;
277+
}
278+
279+
if (isNegative)
280+
{
281+
hours = -hours;
282+
minutes = -minutes;
283+
seconds = -seconds;
284+
microseconds = -microseconds;
285+
}
286+
return new TimeSpan(0, hours, minutes, seconds, microseconds / 1000) + TimeSpan.FromTicks(microseconds % 1000 * 10);
287+
288+
InvalidTimeSpan:
289+
throw new FormatException("Couldn't interpret '{0}' as a valid TimeSpan".FormatInvariant(Encoding.UTF8.GetString(originalValue)));
290+
}
291+
232292
#if NET45
233293
public static Task<T> TaskFromException<T>(Exception exception)
234294
{

tests/MySqlConnector.Tests/MySqlConnector.Tests.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup Condition=" '$(Configuration)' != 'Baseline' ">
4-
<TargetFrameworks>netcoreapp2.0</TargetFrameworks>
4+
<TargetFrameworks>netcoreapp2.1</TargetFrameworks>
55
</PropertyGroup>
66

77
<PropertyGroup Condition=" '$(Configuration)' == 'Baseline' ">
@@ -32,7 +32,7 @@
3232

3333
<ItemGroup Condition=" '$(Configuration)' == 'Baseline' ">
3434
<PackageReference Include="MySql.Data" Version="8.0.11" />
35-
<Compile Remove="ConnectionTests.cs;FakeMySqlServer.cs;FakeMySqlServerConnection.cs;LoadBalancerTests.cs;NormalizeTests.cs;StatementPreparerTests.cs;TypeMapperTests.cs" />
35+
<Compile Remove="ConnectionTests.cs;FakeMySqlServer.cs;FakeMySqlServerConnection.cs;LoadBalancerTests.cs;NormalizeTests.cs;StatementPreparerTests.cs;TypeMapperTests.cs;UtilityTests.cs" />
3636
</ItemGroup>
3737

3838
<ItemGroup Condition=" '$(TargetFramework)' == 'net462' ">
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using System;
2+
using System.Globalization;
3+
using System.Text;
4+
using MySqlConnector.Utilities;
5+
using Xunit;
6+
7+
namespace MySqlConnector.Tests
8+
{
9+
public class UtilityTests
10+
{
11+
[Theory]
12+
[InlineData("00:00:00", "00:00:00")]
13+
[InlineData("00:00:01", "00:00:01")]
14+
[InlineData("00:01:00", "00:01:00")]
15+
[InlineData("00:12:34", "00:12:34")]
16+
[InlineData("01:00:00", "01:00:00")]
17+
[InlineData("12:34:56", "12:34:56")]
18+
[InlineData("-00:00:01", "-00:00:01")]
19+
[InlineData("-00:01:00", "-00:01:00")]
20+
[InlineData("-00:12:34", "-00:12:34")]
21+
[InlineData("-01:00:00", "-01:00:00")]
22+
[InlineData("-12:34:56", "-12:34:56")]
23+
[InlineData("00:00:00.1", "00:00:00.1")]
24+
[InlineData("00:00:00.12", "00:00:00.12")]
25+
[InlineData("00:00:00.123", "00:00:00.123")]
26+
[InlineData("00:00:00.1234", "00:00:00.1234")]
27+
[InlineData("00:00:00.12345", "00:00:00.12345")]
28+
[InlineData("00:00:00.123456", "00:00:00.123456")]
29+
[InlineData("-00:00:00.1", "-00:00:00.1")]
30+
[InlineData("-00:00:00.12", "-00:00:00.12")]
31+
[InlineData("-00:00:00.123", "-00:00:00.123")]
32+
[InlineData("-00:00:00.1234", "-00:00:00.1234")]
33+
[InlineData("-00:00:00.12345", "-00:00:00.12345")]
34+
[InlineData("-00:00:00.123456", "-00:00:00.123456")]
35+
[InlineData("838:59:59", "34.22:59:59")]
36+
[InlineData("838:59:59.999999", "34.22:59:59.999999")]
37+
[InlineData("-838:59:59", "-34.22:59:59")]
38+
[InlineData("-838:59:59.999999", "-34.22:59:59.999999")]
39+
public void ParseTimeSpan(string input, string expectedString)
40+
{
41+
var expected = TimeSpan.ParseExact(expectedString, "c", CultureInfo.InvariantCulture);
42+
var actual = Utility.ParseTimeSpan(Encoding.ASCII.GetBytes(input));
43+
Assert.Equal(expected, actual);
44+
}
45+
46+
[Theory]
47+
[InlineData("0")]
48+
[InlineData("0:0:0")]
49+
[InlineData("--01:00:00")]
50+
[InlineData("00-00-00")]
51+
[InlineData("00:00:60")]
52+
[InlineData("00:60:00")]
53+
[InlineData("999:00:00")]
54+
[InlineData("00:00:00.1234567")]
55+
public void ParseTimeSpanFails(string input)
56+
{
57+
Assert.Throws<FormatException>(() => Utility.ParseTimeSpan(Encoding.ASCII.GetBytes(input)));
58+
}
59+
}
60+
}

0 commit comments

Comments
 (0)