Skip to content

replication: return string when datetime's day or month is 00 #1047

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 30 additions & 19 deletions replication/row_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -1316,7 +1316,7 @@ func (e *RowsEvent) decodeValue(data []byte, tp byte, meta uint16, isPartial boo
n = 4
t := binary.LittleEndian.Uint32(data)
if t == 0 {
v = formatZeroTime(0, 0)
v = "0000-00-00 00:00:00"
} else {
v = e.parseFracTime(fracTime{
Time: time.Unix(int64(t), 0),
Expand All @@ -1331,26 +1331,37 @@ func (e *RowsEvent) decodeValue(data []byte, tp byte, meta uint16, isPartial boo
n = 8
i64 := binary.LittleEndian.Uint64(data)
if i64 == 0 {
v = formatZeroTime(0, 0)
v = "0000-00-00 00:00:00"
} else {
d := i64 / 1000000
t := i64 % 1000000
v = e.parseFracTime(fracTime{
Time: time.Date(
int(d/10000),
time.Month((d%10000)/100),
int(d%100),
int(t/10000),
int((t%10000)/100),
int(t%100),
0,
time.UTC,
),
Dec: 0,
})
years := int(d / 10000)
months := int(d%10000) / 100
days := int(d % 100)
hours := int(t / 10000)
minutes := int(t%10000) / 100
seconds := int(t % 100)
if !e.parseTime || months == 0 || days == 0 {
v = fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d",
years, months, days, hours, minutes, seconds)
} else {
v = e.parseFracTime(fracTime{
Time: time.Date(
years,
time.Month(months),
days,
hours,
minutes,
seconds,
0,
time.UTC,
),
Dec: 0,
})
}
}
case mysql.MYSQL_TYPE_DATETIME2:
v, n, err = decodeDatetime2(data, meta)
v, n, err = decodeDatetime2(data, meta, e.parseTime)
v = e.parseFracTime(v)
case mysql.MYSQL_TYPE_TIME:
n = 3
Expand Down Expand Up @@ -1675,7 +1686,7 @@ func decodeTimestamp2(data []byte, dec uint16, timestampStringLocation *time.Loc

const DATETIMEF_INT_OFS int64 = 0x8000000000

func decodeDatetime2(data []byte, dec uint16) (interface{}, int, error) {
func decodeDatetime2(data []byte, dec uint16, parseTime bool) (interface{}, int, error) {
// get datetime binary length
n := int(5 + (dec+1)/2)

Expand Down Expand Up @@ -1725,8 +1736,8 @@ func decodeDatetime2(data []byte, dec uint16) (interface{}, int, error) {
// minute = 0 = 0b000000
// second = 0 = 0b000000
// integer value = 0b1100100000010110000100000000000000000 = 107420450816
if intPart < 107420450816 {
return formatBeforeUnixZeroTime(year, month, day, hour, minute, second, int(frac), int(dec)), n, nil
if !parseTime || intPart < 107420450816 || month == 0 || day == 0 {
return formatDatetime(year, month, day, hour, minute, second, int(frac), int(dec)), n, nil
}

return fracTime{
Expand Down
27 changes: 16 additions & 11 deletions replication/row_event_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,8 @@ func TestDecodeDatetime2(t *testing.T) {
}{
{[]byte("\xfe\xf3\xff\x7e\xfb"), 0, true, "9999-12-31 23:59:59"},
{[]byte("\x99\x9a\xb8\xf7\xaa"), 0, true, "2016-10-28 15:30:42"},
{[]byte("\x99\x98\x38\xf7\xaa"), 0, false, "2016-00-28 15:30:42"},
{[]byte("\x99\x9a\x80\xf7\xaa"), 0, false, "2016-10-00 15:30:42"},
{[]byte("\x99\x02\xc2\x00\x00"), 0, true, "1970-01-01 00:00:00"},
{[]byte("\x80\x00\x00\x00\x00"), 0, false, "0000-00-00 00:00:00"},
{[]byte("\x80\x00\x02\xf1\x05"), 0, false, "0000-00-01 15:04:05"},
Expand All @@ -684,17 +686,20 @@ func TestDecodeDatetime2(t *testing.T) {
{[]byte("\x80\x03\x82\x00\x00\x01\xe2\x40"), uint16(6), false, "0001-01-01 00:00:00.123456"},
}
for _, tc := range testcases {
value, _, err := decodeDatetime2(tc.data, tc.dec)
require.NoError(t, err)
switch v := value.(type) {
case fracTime:
require.True(t, tc.getFracTime)
require.Equal(t, tc.expected, v.String())
case string:
require.False(t, tc.getFracTime)
require.Equal(t, tc.expected, v)
default:
require.FailNow(t, "invalid value type: %T", value)
for _, parseTime := range []bool{true, false} {
value, _, err := decodeDatetime2(tc.data, tc.dec, parseTime)
require.NoError(t, err)
switch v := value.(type) {
case fracTime:
require.True(t, parseTime)
require.True(t, tc.getFracTime)
require.Equal(t, tc.expected, v.String())
case string:
require.False(t, parseTime && tc.getFracTime)
require.Equal(t, tc.expected, v)
default:
require.FailNow(t, "invalid value type: %T", value)
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion replication/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func formatZeroTime(frac int, dec int) string {
return s[0 : len(s)-(6-dec)]
}

func formatBeforeUnixZeroTime(year, month, day, hour, minute, second, frac, dec int) string {
func formatDatetime(year, month, day, hour, minute, second, frac, dec int) string {
if dec == 0 {
return fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, minute, second)
}
Expand Down
Loading