Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b1a5851
update
daviszhen Jul 16, 2026
8fece46
update
daviszhen Jul 16, 2026
16ce330
update
daviszhen Jul 16, 2026
fa4c3e1
update
daviszhen Jul 16, 2026
b718989
update
daviszhen Jul 16, 2026
3f1577a
update
daviszhen Jul 16, 2026
a91bd09
update
daviszhen Jul 17, 2026
31e1e3c
update
daviszhen Jul 17, 2026
f80bdfc
update
daviszhen Jul 20, 2026
aa284b0
update
daviszhen Jul 20, 2026
3ea9501
update
daviszhen Jul 20, 2026
8c17d5d
Merge branch 'main' into 0716-fix-temporal2
daviszhen Jul 20, 2026
ea9198f
update
daviszhen Jul 20, 2026
dd4e4bf
update
daviszhen Jul 20, 2026
ba480b9
Merge branch 'main' into 0716-fix-temporal2
daviszhen Jul 20, 2026
28c637a
fix
daviszhen Jul 20, 2026
1efebd6
Merge branch 'main' into 0716-fix-temporal2
daviszhen Jul 20, 2026
86d3137
update
daviszhen Jul 21, 2026
ef4fe5e
update
daviszhen Jul 21, 2026
b1d696c
Merge branch 'main' into 0716-fix-temporal2
daviszhen Jul 21, 2026
0212a9d
update
daviszhen Jul 21, 2026
884663d
Merge branch 'main' into 0716-fix-temporal2
daviszhen Jul 21, 2026
94fae6f
Merge branch 'main' into 0716-fix-temporal2
daviszhen Jul 21, 2026
f11567a
Merge branch 'main' into 0716-fix-temporal2
daviszhen Jul 22, 2026
8c6a8d8
update
daviszhen Jul 22, 2026
17de297
update
daviszhen Jul 22, 2026
8868b98
update
daviszhen Jul 22, 2026
aff162f
Merge branch 'main' into 0716-fix-temporal2
daviszhen Jul 22, 2026
52864ba
update
daviszhen Jul 22, 2026
95fc3d5
Merge branch 'main' into 0716-fix-temporal2
daviszhen Jul 22, 2026
d22c640
Merge branch 'main' into 0716-fix-temporal2
daviszhen Jul 22, 2026
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
16 changes: 16 additions & 0 deletions pkg/container/types/date.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ const (
MinDateYear = 1
MaxMonthInYear = 12
MinMonthInYear = 1
ZeroDate = Date(-1)
)

type TimeType int32
Expand Down Expand Up @@ -93,6 +94,9 @@ func isAllDigit(s string) bool {
// 3.yyyymmdd
func ParseDateCast(s string) (Date, error) {
s = strings.TrimSpace(s)
if isZeroDatetimeString(s) {
return ZeroDate, nil
}
if len(s) < 7 && isAllDigit(s) {
return -1, moerr.NewInvalidArgNoCtx("parsedate", s)
}
Expand Down Expand Up @@ -443,6 +447,9 @@ func init() {

// Year takes a date and returns an uint16 number as the year of this date
func (d Date) Year() uint16 {
if d == ZeroDate {
return 0
}
dayNum := int32(d)
insideDayInfoTable := dayNum >= dayNumOfTableEpoch && dayNum < dayNumOfTableEpoch+dayInfoTableSize
if insideDayInfoTable {
Expand Down Expand Up @@ -518,6 +525,9 @@ func (d Date) Quarter() uint32 {
}

func (d Date) Calendar(full bool) (year int32, month, day uint8, yday uint16) {
if d == ZeroDate {
return 0, 0, 0, 0
}
// Account for 400 year cycles.
n := d / daysPer400Years
y := 400 * n
Expand Down Expand Up @@ -853,6 +863,9 @@ func isLeap(year int32) bool {
}

func (d Date) ToDatetime() Datetime {
if d == ZeroDate {
return ZeroDatetime
}
return Datetime(int64(d) * SecsPerDay * MicroSecsPerSec)
}

Expand All @@ -861,6 +874,9 @@ func (d Date) ToTime() Time {
}

func (d Date) ToTimestamp(loc *time.Location) Timestamp {
if d == ZeroDate {
return ZeroTimestamp
}
year, mon, day, _ := d.Calendar(true)
t := time.Date(int(year), time.Month(mon), int(day), 0, 0, 0, 0, loc)
return Timestamp(t.UnixMicro() + unixEpochMicroSecs)
Expand Down
58 changes: 56 additions & 2 deletions pkg/container/types/datetime.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,19 +47,30 @@ var (
)

const (
// ZeroDatetime is the zero value for date Time '0000-01-01 00:00:00'.
ZeroDatetime = Datetime(0)
// DatetimeEpoch is the internal epoch for 0001-01-01 00:00:00.
DatetimeEpoch = Datetime(0)
// ZeroDatetime represents MySQL's 0000-00-00 00:00:00 value.
ZeroDatetime = Datetime(-1)
)

// The Datetime type holds number of microseconds since January 1, year 1 in Gregorian calendar

func (dt Datetime) String() string {
if dt == ZeroDatetime {
return "0000-00-00 00:00:00"
}
y, m, d, _ := dt.ToDate().Calendar(true)
hour, minute, sec := dt.Clock()
return fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", y, m, d, hour, minute, sec)
}

func (dt Datetime) String2(scale int32) string {
if dt == ZeroDatetime {
if scale > 0 {
return "0000-00-00 00:00:00." + strings.Repeat("0", int(scale))
}
return "0000-00-00 00:00:00"
}
y, m, d, _ := dt.ToDate().Calendar(true)
hour, minute, sec := dt.Clock()

Expand Down Expand Up @@ -105,6 +116,9 @@ func (dt Datetime) String2(scale int32) string {
// "1999-09-09 11:11:" "1999-09-09 11:11:00.000"
func ParseDatetime(s string, scale int32) (Datetime, error) {
s = strings.TrimSpace(s)
if isZeroDatetimeString(s) {
return ZeroDatetime, nil
}
if len(s) < 14 {
if d, err := ParseDateCast(s); err == nil {
return d.ToDatetime(), nil
Expand Down Expand Up @@ -297,6 +311,28 @@ func ParseDatetime(s string, scale int32) (Datetime, error) {
return result, nil
}

func isZeroDatetimeString(s string) bool {
base := s
if dot := strings.IndexByte(s, '.'); dot >= 0 {
base = s[:dot]
fraction := s[dot+1:]
if fraction == "" || !isAllDigit(fraction) {
return false
}
for i := range fraction {
if fraction[i] != '0' {
return false
}
}
}
switch base {
case "0000-00-00", "00000000", "0000-00-00 00:00:00", "0000-00-00T00:00:00", "00000000000000":
return true
default:
return false
}
}

// validTimeInDay return true if hour, minute and second can be a time during a day
func ValidTimeInDay(h, m, s uint8) bool {
if h < minHourInDay || h > maxHourInDay {
Expand All @@ -312,6 +348,9 @@ func ValidTimeInDay(h, m, s uint8) bool {
}

func (dt Datetime) UnixTimestamp(loc *time.Location) int64 {
if dt == ZeroDatetime {
return -1
}
return dt.ConvertToGoTime(loc).Unix()
}

Expand Down Expand Up @@ -339,6 +378,9 @@ func UTC() Datetime {
}

func (dt Datetime) ToDate() Date {
if dt == ZeroDatetime {
return ZeroDate
}
return Date(dt.sec() / SecsPerDay)
}

Expand Down Expand Up @@ -389,6 +431,9 @@ func (dt Datetime) ToTime(scale int32) Time {
// - 6: microseconds (full precision, no truncation)
// - >6: treated as scale 6 (full precision)
func (dt Datetime) TruncateToScale(scale int32) Datetime {
if dt == ZeroDatetime {
return ZeroDatetime
}
// For scale >= 6, return full precision (no truncation)
if scale >= 6 {
return dt
Expand All @@ -410,6 +455,9 @@ func (dt Datetime) TruncateToScale(scale int32) Datetime {
}

func (dt Datetime) Clock() (hour, minute, sec int8) {
if dt == ZeroDatetime {
return 0, 0, 0
}
t := dt.sec() % SecsPerDay
hour = int8(t / SecsPerHour)
minute = int8(t % SecsPerHour / SecsPerMinute)
Expand Down Expand Up @@ -571,6 +619,9 @@ func (dt Datetime) ConvertToMonth(secondDt Datetime) int64 {
}

func (dt Datetime) MicroSec() int64 {
if dt == ZeroDatetime {
return 0
}
return int64(dt) % MicroSecsPerSec
}

Expand Down Expand Up @@ -616,6 +667,9 @@ func (dt Datetime) YearWeek(mode int) (year int, week int) {
}

func (dt Datetime) ToTimestamp(loc *time.Location) Timestamp {
if dt == ZeroDatetime {
return ZeroTimestamp
}
return Timestamp(dt.ConvertToGoTime(loc).UnixMicro() + unixEpochMicroSecs)
}

Expand Down
47 changes: 45 additions & 2 deletions pkg/container/types/timestamp.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ package types
import (
"fmt"
"strconv"
"strings"
"time"
"unsafe"

Expand All @@ -50,6 +51,8 @@ var (
var TimestampMinValue Timestamp
var TimestampMaxValue Timestamp

const ZeroTimestamp = Timestamp(-1)

// the range for TIMESTAMP values is '1970-01-01 00:00:01.000000' to '2038-01-19 03:14:07.999999'.
// Note: TimestampMinValue enforces the minimum, but TimestampMaxValue is set to 9999-12-31
// to allow values beyond MySQL's 2038 limit (we don't enforce maximum value restriction).
Expand All @@ -59,6 +62,9 @@ func init() {
}

func (ts Timestamp) String() string {
if ts == ZeroTimestamp {
return "0000-00-00 00:00:00.000000 UTC"
}
dt := Datetime(int64(ts))
y, m, d, _ := dt.ToDate().Calendar(true)
hour, minute, sec := dt.Clock()
Expand All @@ -68,6 +74,12 @@ func (ts Timestamp) String() string {

// String2 stringify timestamp, including its fractional seconds precision part(fsp)
func (ts Timestamp) String2(loc *time.Location, scale int32) string {
if ts == ZeroTimestamp {
if scale > 0 {
return "0000-00-00 00:00:00." + strings.Repeat("0", int(scale))
}
return "0000-00-00 00:00:00"
}
t := time.UnixMicro(int64(ts) - unixEpochMicroSecs).In(loc)
y, m, d := t.Date()
hour, minute, sec := t.Clock()
Expand All @@ -83,18 +95,30 @@ func (ts Timestamp) String2(loc *time.Location, scale int32) string {
}

func (ts Timestamp) Unix() int64 {
if ts == ZeroTimestamp {
return -1
}
return (int64(ts) - unixEpochMicroSecs) / MicroSecsPerSec
}

func (ts Timestamp) UnixToFloat() float64 {
if ts == ZeroTimestamp {
return -1
}
return float64(int64(ts)-unixEpochMicroSecs) / MicroSecsPerSec
}

func (ts Timestamp) UnixToDecimal64() (Decimal64, error) {
if ts == ZeroTimestamp {
return Decimal64(^uint64(0)), nil
}
return Decimal64(int64(ts) - unixEpochMicroSecs), nil
}

func (ts Timestamp) UnixToDecimal128() (Decimal128, error) {
if ts == ZeroTimestamp {
return Decimal128{B0_63: ^uint64(0), B64_127: ^uint64(0)}, nil
}
return Decimal128{uint64(int64(ts) - unixEpochMicroSecs), 0}, nil
}

Expand Down Expand Up @@ -159,6 +183,9 @@ func ParseTimestamp(loc *time.Location, s string, scale int32) (Timestamp, error
if err != nil {
return -1, moerr.NewInvalidArgNoCtx("parse timestamp", s)
}
if dt == ZeroDatetime {
return ZeroTimestamp, nil
}

result := dt.ToTimestamp(loc)
//for issue5305, do not do this check
Expand Down Expand Up @@ -194,10 +221,18 @@ func TimestampToDatetime(loc *time.Location, xs []Timestamp, rs []Datetime) ([]D
if len(locPtr.zone) == 1 {
offset := int64(locPtr.zone[0].offset) * MicroSecsPerSec
for i, x := range xsInInt64 {
rsInInt64[i] = x + offset
if Timestamp(x) == ZeroTimestamp {
rsInInt64[i] = int64(ZeroDatetime)
} else {
rsInInt64[i] = x + offset
}
}
} else {
for i, x := range xsInInt64 {
if Timestamp(x) == ZeroTimestamp {
rsInInt64[i] = int64(ZeroDatetime)
continue
}
t := time.UnixMicro(x - unixEpochMicroSecs).In(loc)
_, offset := t.Zone()
rsInInt64[i] = x + int64(offset)*MicroSecsPerSec
Expand All @@ -207,6 +242,9 @@ func TimestampToDatetime(loc *time.Location, xs []Timestamp, rs []Datetime) ([]D
}

func (ts Timestamp) ToDatetime(loc *time.Location) Datetime {
if ts == ZeroTimestamp {
return ZeroDatetime
}
t := time.UnixMicro(int64(ts) - unixEpochMicroSecs).In(loc)
_, offset := t.Zone()
return Datetime(ts) + Datetime(offset)*MicroSecsPerSec
Expand All @@ -218,6 +256,9 @@ func (ts Timestamp) ToDatetime(loc *time.Location) Datetime {
// - 1-5: fractional seconds with corresponding precision
// - 6: microseconds (full precision, no truncation)
func (ts Timestamp) TruncateToScale(scale int32) Timestamp {
if ts == ZeroTimestamp {
return ZeroTimestamp
}
if scale == 6 {
return ts
}
Expand Down Expand Up @@ -247,8 +288,10 @@ func CurrentTimestamp() Timestamp {
return Timestamp(time.Now().UnixMicro() + unixEpochMicroSecs)
}

// ValidTimestamp reports whether a value is representable by a TIMESTAMP column.
// The zero sentinel remains subject to the statement's sql_mode policy.
func ValidTimestamp(timestamp Timestamp) bool {
return timestamp > TimestampMinValue
return timestamp == ZeroTimestamp || timestamp >= TimestampMinValue
}

func UnixToTimestamp(ts int64) Timestamp {
Expand Down
7 changes: 7 additions & 0 deletions pkg/container/types/timestamp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,13 @@ func TestParseTimestamp(t *testing.T) {
//require.Error(t, err)
}

func TestValidTimestampColumnLowerBound(t *testing.T) {
require.True(t, ValidTimestamp(ZeroTimestamp))
require.True(t, ValidTimestamp(TimestampMinValue))
require.True(t, ValidTimestamp(TimestampMinValue+1))
require.False(t, ValidTimestamp(TimestampMinValue-1))
}

func TestLocation(t *testing.T) {
loc := time.FixedZone("test", 8*3600)
locPtr := (*unsafeLoc)(unsafe.Pointer(loc))
Expand Down
Loading
Loading