diff --git a/lib/typing/ext/parse.go b/lib/typing/ext/parse.go index 2b8439554..e851a0888 100644 --- a/lib/typing/ext/parse.go +++ b/lib/typing/ext/parse.go @@ -41,10 +41,11 @@ func ParseFromInterface(val any, kindType ExtendedTimeKindType) (time.Time, erro } func ParseDateTime(value string, kindType ExtendedTimeKindType) (time.Time, error) { - // TODO: Support TimestampNTZKindType switch kindType { + case TimestampNTZKindType: + return parseTimestampNTZ(value) case TimestampTZKindType: - return parseDateTime(value) + return parseTimestampTZ(value) case DateKindType: // Try date first if ts, err := parseDate(value); err == nil { @@ -52,7 +53,7 @@ func ParseDateTime(value string, kindType ExtendedTimeKindType) (time.Time, erro } // If that doesn't work, try timestamp - if ts, err := parseDateTime(value); err == nil { + if ts, err := parseTimestampTZ(value); err == nil { return ts, nil } case TimeKindType: @@ -62,7 +63,7 @@ func ParseDateTime(value string, kindType ExtendedTimeKindType) (time.Time, erro } // If that doesn't work, try timestamp - if ts, err := parseDateTime(value); err == nil { + if ts, err := parseTimestampTZ(value); err == nil { return ts, nil } } @@ -70,7 +71,16 @@ func ParseDateTime(value string, kindType ExtendedTimeKindType) (time.Time, erro return time.Time{}, fmt.Errorf("unsupported value: %q, kindType: %q", value, kindType) } -func parseDateTime(value string) (time.Time, error) { +func parseTimestampNTZ(value string) (time.Time, error) { + ts, err := ParseTimeExactMatch(RFC3339NoTZ, value) + if err != nil { + return time.Time{}, fmt.Errorf("unsupported value: %q: %w", value, err) + } + + return ts, nil +} + +func parseTimestampTZ(value string) (time.Time, error) { for _, supportedDateTimeLayout := range supportedDateTimeLayouts { if ts, err := ParseTimeExactMatch(supportedDateTimeLayout, value); err == nil { return ts, nil diff --git a/lib/typing/ext/parse_test.go b/lib/typing/ext/parse_test.go index e685ef531..c278c626f 100644 --- a/lib/typing/ext/parse_test.go +++ b/lib/typing/ext/parse_test.go @@ -83,11 +83,42 @@ func TestParseFromInterfaceDate(t *testing.T) { } } -func TestParseExtendedDateTime_Timestamp(t *testing.T) { +func TestParseExtendedDateTime_TimestampTZ(t *testing.T) { tsString := "2023-04-24T17:29:05.69944Z" extTime, err := ParseDateTime(tsString, TimestampTZKindType) assert.NoError(t, err) - assert.Equal(t, "2023-04-24T17:29:05.69944Z", extTime.Format(time.RFC3339Nano)) + assert.Equal(t, tsString, extTime.Format(time.RFC3339Nano)) +} + +func TestParseExtendedDateTime_TimestampNTZ(t *testing.T) { + { + // No fractional seconds + tsString := "2023-04-24T17:29:05" + extTime, err := ParseDateTime(tsString, TimestampNTZKindType) + assert.NoError(t, err) + assert.Equal(t, tsString, extTime.Format(RFC3339NoTZ)) + } + { + // ms + tsString := "2023-04-24T17:29:05.123" + extTime, err := ParseDateTime(tsString, TimestampNTZKindType) + assert.NoError(t, err) + assert.Equal(t, tsString, extTime.Format(RFC3339NoTZ)) + } + { + // microseconds + tsString := "2023-04-24T17:29:05.123456" + extTime, err := ParseDateTime(tsString, TimestampNTZKindType) + assert.NoError(t, err) + assert.Equal(t, tsString, extTime.Format(RFC3339NoTZ)) + } + { + // ns + tsString := "2023-04-24T17:29:05.123456789" + extTime, err := ParseDateTime(tsString, TimestampNTZKindType) + assert.NoError(t, err) + assert.Equal(t, tsString, extTime.Format(RFC3339NoTZ)) + } } func TestTimeLayout(t *testing.T) { diff --git a/lib/typing/ext/variables.go b/lib/typing/ext/variables.go index defea4453..0a1f0387c 100644 --- a/lib/typing/ext/variables.go +++ b/lib/typing/ext/variables.go @@ -48,6 +48,7 @@ const TimezoneOffsetFormat = "Z07:00" // RFC3339 variants const ( + RFC3339NoTZ = "2006-01-02T15:04:05.999999999" RFC3339MillisecondUTC = "2006-01-02T15:04:05.000Z" RFC3339MicrosecondUTC = "2006-01-02T15:04:05.000000Z" RFC3339NanosecondUTC = "2006-01-02T15:04:05.000000000Z"