Skip to content

Commit e666680

Browse files
committed
fixed time types
1 parent 3246f65 commit e666680

File tree

4 files changed

+160
-9
lines changed

4 files changed

+160
-9
lines changed

src/SciSharp.MySQL.Replication/Events/LogEvent.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ static LogEvent()
6868
DataTypes[(int)ColumnType.VARCHAR] = new VarCharType();
6969
DataTypes[(int)ColumnType.DATETIME] = new DateTimeType();
7070
DataTypes[(int)ColumnType.DATETIME_V2] = new DateTimeV2Type();
71+
DataTypes[(int)ColumnType.TIME] = new TimeType();
72+
DataTypes[(int)ColumnType.TIME_V2] = new TimeV2Type();
7173
//DataTypes[(int)ColumnType.TIMESTAMP] = new TimestampType();
7274
DataTypes[(int)ColumnType.ENUM] = new EnumType();
7375
DataTypes[(int)ColumnType.SET] = new SetType();

src/SciSharp.MySQL.Replication/Types/TimeType.cs

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace SciSharp.MySQL.Replication.Types
99
/// Represents the MySQL TIME data type.
1010
/// </summary>
1111
/// <remarks>
12-
/// Handles the reading and conversion of MySQL TIME values.
12+
/// Handles the reading and conversion of MySQL TIME values from binary log.
1313
/// </remarks>
1414
class TimeType : IMySQLDataType
1515
{
@@ -18,11 +18,48 @@ class TimeType : IMySQLDataType
1818
/// </summary>
1919
/// <param name="reader">The sequence reader containing the bytes to read.</param>
2020
/// <param name="columnMetadata">Metadata for the column.</param>
21-
/// <returns>An object representing the MySQL TIME value.</returns>
22-
/// <exception cref="NotImplementedException">This method has not yet been implemented.</exception>
21+
/// <returns>A TimeSpan representing the MySQL TIME value.</returns>
2322
public object ReadValue(ref SequenceReader<byte> reader, ColumnMetadata columnMetadata)
2423
{
25-
throw new NotImplementedException();
24+
// In binary log format, TIME is encoded as a 3-byte integer
25+
int encodedValue = 0;
26+
27+
reader.TryRead(out byte b0);
28+
reader.TryRead(out byte b1);
29+
reader.TryRead(out byte b2);
30+
31+
// Combine the 3 bytes into an integer (little-endian)
32+
encodedValue = b0 | (b1 << 8) | (b2 << 16);
33+
34+
// Check if negative (bit 23 is the sign bit)
35+
bool isNegative = (encodedValue & 0x800000) != 0;
36+
37+
// Handle negative values
38+
if (isNegative)
39+
{
40+
// Clear the sign bit for calculations
41+
encodedValue &= 0x7FFFFF;
42+
}
43+
44+
// Binary log format has time in a packed decimal format:
45+
// HHHHHH MMMMMM SSSSSS (each field taking 6 bits in binary log)
46+
// But we need to convert it to HHMMSS for TimeSpan
47+
48+
int hours = encodedValue / 10000;
49+
int minutes = (encodedValue % 10000) / 100;
50+
int seconds = encodedValue % 100;
51+
52+
// Validate time components
53+
if (hours > 838 || minutes > 59 || seconds > 59)
54+
{
55+
throw new InvalidOperationException($"Invalid TIME value: {encodedValue}");
56+
}
57+
58+
// Create TimeSpan (MySQL TIME can represent larger ranges than .NET TimeSpan)
59+
TimeSpan result = new TimeSpan(hours, minutes, seconds);
60+
61+
// Apply sign if needed
62+
return isNegative ? result.Negate() : result;
2663
}
2764
}
2865
}

src/SciSharp.MySQL.Replication/Types/TimeV2Type.cs

Lines changed: 116 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,130 @@ namespace SciSharp.MySQL.Replication.Types
1111
/// <remarks>
1212
/// Handles the reading and conversion of MySQL TIME values with fractional seconds.
1313
/// </remarks>
14-
class TimeV2Type : IMySQLDataType
14+
class TimeV2Type : IMySQLDataType, IColumnMetadataLoader
1515
{
16+
/// <summary>
17+
/// Loads the fractional seconds precision from the column metadata.
18+
/// </summary>
19+
/// <param name="columnMetadata">The column metadata containing the precision value.</param>
20+
public void LoadMetadataValue(ColumnMetadata columnMetadata)
21+
{
22+
// For TIME2 type, the metadata value represents the precision of fractional seconds
23+
// MySQL supports precision values from 0 to 6 (microsecond precision)
24+
columnMetadata.Options = new TimeV2Options
25+
{
26+
FractionalSecondsPrecision = columnMetadata.MetadataValue[0]
27+
};
28+
}
29+
1630
/// <summary>
1731
/// Reads a TIME2 value from the binary log.
1832
/// </summary>
1933
/// <param name="reader">The sequence reader containing the bytes to read.</param>
2034
/// <param name="columnMetadata">Metadata for the column defining fractional second precision.</param>
21-
/// <returns>An object representing the MySQL TIME2 value.</returns>
22-
/// <exception cref="NotImplementedException">This method has not yet been implemented.</exception>
35+
/// <returns>A TimeSpan representing the MySQL TIME2 value.</returns>
2336
public object ReadValue(ref SequenceReader<byte> reader, ColumnMetadata columnMetadata)
2437
{
25-
throw new NotImplementedException();
38+
// Get the fractional seconds precision from metadata (0-6)
39+
int fsp = columnMetadata.Options is TimeV2Options options ? options.FractionalSecondsPrecision : 0;
40+
41+
// Read the integer part (3 bytes)
42+
byte intPartByte1, intPartByte2, intPartByte3;
43+
reader.TryRead(out intPartByte1);
44+
reader.TryRead(out intPartByte2);
45+
reader.TryRead(out intPartByte3);
46+
47+
// Combine into a 24-bit integer, bigendian
48+
int intPart = (intPartByte1 << 16) | (intPartByte2 << 8) | intPartByte3;
49+
50+
// In MySQL 5.6.4+ TIME2 format:
51+
// Bit 1 (MSB): Sign bit (1=negative, 0=positive)
52+
// Bits 2-24: Packed BCD encoding of time value
53+
bool isNegative = ((intPart & 0x800000) == 0); // In MySQL TIME2, 0 is negative, 1 is positive
54+
55+
// If negative, apply two's complement
56+
if (isNegative)
57+
{
58+
intPart = ~intPart + 1;
59+
intPart &= 0x7FFFFF; // Keep only the 23 bits for the absolute value
60+
}
61+
62+
// TIME2 is packed in a special format:
63+
// Bits 2-13: Hours (12 bits)
64+
// Bits 14-19: Minutes (6 bits)
65+
// Bits 20-25: Seconds (6 bits)
66+
int hours = (intPart >> 12) & 0x3FF;
67+
int minutes = (intPart >> 6) & 0x3F;
68+
int seconds = intPart & 0x3F;
69+
70+
// Read fractional seconds if precision > 0
71+
int microseconds = 0;
72+
if (fsp > 0)
73+
{
74+
// Calculate bytes needed for the requested precision
75+
int fractionalBytes = (fsp + 1) / 2;
76+
int fraction = 0;
77+
78+
// Read bytes for fractional seconds
79+
for (int i = 0; i < fractionalBytes; i++)
80+
{
81+
byte b;
82+
reader.TryRead(out b);
83+
fraction = (fraction << 8) | b;
84+
}
85+
86+
// Convert to microseconds based on precision
87+
int scaleFactor = 1000000;
88+
switch (fsp)
89+
{
90+
case 1: scaleFactor = 100000; break;
91+
case 2: scaleFactor = 10000; break;
92+
case 3: scaleFactor = 1000; break;
93+
case 4: scaleFactor = 100; break;
94+
case 5: scaleFactor = 10; break;
95+
case 6: scaleFactor = 1; break;
96+
}
97+
98+
microseconds = fraction * scaleFactor;
99+
}
100+
101+
// Create TimeSpan
102+
TimeSpan result;
103+
104+
if (hours >= 24)
105+
{
106+
// For large hour values, convert to days + remaining hours
107+
int days = hours / 24;
108+
int remainingHours = hours % 24;
109+
110+
// Create TimeSpan with days, hours, minutes, seconds, ms
111+
result = new TimeSpan(days, remainingHours, minutes, seconds, microseconds / 1000);
112+
113+
// Add remaining microseconds as ticks (1 tick = 100 nanoseconds, 1 microsecond = 10 ticks)
114+
if (microseconds % 1000 > 0)
115+
{
116+
result = result.Add(TimeSpan.FromTicks((microseconds % 1000) * 10));
117+
}
118+
}
119+
else
120+
{
121+
// Standard case for hours < 24
122+
result = new TimeSpan(0, hours, minutes, seconds, microseconds / 1000);
123+
124+
// Add microsecond precision as ticks
125+
if (microseconds % 1000 > 0)
126+
{
127+
result = result.Add(TimeSpan.FromTicks((microseconds % 1000) * 10));
128+
}
129+
}
130+
131+
// Apply sign
132+
return isNegative ? result.Negate() : result;
26133
}
27134
}
135+
136+
class TimeV2Options
137+
{
138+
public int FractionalSecondsPrecision { get; set; } = 0;
139+
}
28140
}

tests/Test/DataTypesTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ await TestDataType<DateTime>("date_table", currentValue, currentValue.AddDays(5)
139139
});
140140
}
141141

142-
//[Fact]
142+
[Fact]
143143
public async Task TestTimeType()
144144
{
145145
var currentValue = new TimeSpan(10, 30, 45);

0 commit comments

Comments
 (0)