Skip to content

Commit

Permalink
WIP.
Browse files Browse the repository at this point in the history
  • Loading branch information
Tang8330 committed Jul 24, 2024
1 parent 274cf8d commit 91ca180
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 18 deletions.
19 changes: 19 additions & 0 deletions lib/debezium/converters/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package converters
import (
"fmt"
"strings"
"time"

"github.com/artie-labs/transfer/lib/typing/ext"
)
Expand Down Expand Up @@ -33,3 +34,21 @@ func ConvertDateTimeWithTimezone(value any) (*ext.ExtendedTime, error) {

return nil, fmt.Errorf("failed to parse %q, err: %w", dtString, err)
}

func ConvertTimeWithTimezone(value any) (*ext.ExtendedTime, error) {
valString, isOk := value.(string)
if !isOk {
return nil, fmt.Errorf("expected string got '%v' with type %T", value, value)
}

var err error
var ts time.Time
for _, supportedTimeFormat := range ext.SupportedTimeWithTimezoneFormats {
ts, err = ext.ParseTimeExactMatch(supportedTimeFormat, valString)
if err == nil {
return ext.NewExtendedTime(ts, ext.TimeKindType, supportedTimeFormat), nil
}
}

return nil, fmt.Errorf("failed to parse %q: %w", valString, err)
}
31 changes: 31 additions & 0 deletions lib/debezium/converters/time_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,34 @@ func TestConvertDateTimeWithTimezone(t *testing.T) {
assert.Equal(t, expectedExtTime, val)
}
}

func TestConvertTimeWithTimezone(t *testing.T) {
{
// What Debezium would produce
ts, err := ConvertTimeWithTimezone("23:02:06.745116Z")
assert.NoError(t, err)
expectedTs := &ext.ExtendedTime{
Time: time.Date(0, 1, 1, 23, 2, 6, 745116000, time.UTC),
NestedKind: ext.NestedKind{
Type: ext.TimeKindType,
Format: "15:04:05.000000Z",
},
}

assert.Equal(t, expectedTs, ts)
}
{
// What Reader would produce
ts, err := ConvertTimeWithTimezone("23:02:06.745116")
assert.NoError(t, err)
expectedTs := &ext.ExtendedTime{
Time: time.Date(0, 1, 1, 23, 2, 6, 745116000, time.UTC),
NestedKind: ext.NestedKind{
Type: ext.TimeKindType,
Format: "15:04:05.000000",
},
}

assert.Equal(t, expectedTs, ts)
}
}
2 changes: 2 additions & 0 deletions lib/debezium/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ func (f Field) ParseValue(value any) (any, error) {
return f.DecodeDebeziumVariableDecimal(value)
case DateTimeWithTimezone:
return converters.ConvertDateTimeWithTimezone(value)
case TimeWithTimezone:
return converters.ConvertTimeWithTimezone(value)
case
Timestamp,
MicroTimestamp,
Expand Down
28 changes: 21 additions & 7 deletions lib/typing/ext/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,25 @@ func ParseFromInterface(val any, additionalDateFormats []string) (*ExtendedTime,
return extendedTime, nil
}

// ParseTimeExactMatch is a wrapper around time.Parse() and will return an extra boolean to indicate if it was an exact match or not.
// ParseTimeExactMatch - This function is the same as `ParseTimeExactMatchLegacy` with the only exception that it'll return if it was not an exact match
func ParseTimeExactMatch(layout, potentialDateTimeString string) (time.Time, error) {
ts, err := time.Parse(layout, potentialDateTimeString)
if err != nil {
return time.Time{}, err
}

if ts.Format(layout) != potentialDateTimeString {
return time.Time{}, fmt.Errorf("failed to parse %q with layout %q", potentialDateTimeString, layout)
}

return ts, nil
}

// TODO: Remove callers from this.
// ParseTimeExactMatchLegacy is a wrapper around time.Parse() and will return an extra boolean to indicate if it was an exact match or not.
// Parameters: layout, potentialDateTimeString
// Returns: time.Time object, exactLayout (boolean), error
func ParseTimeExactMatch(layout, potentialDateTimeString string) (time.Time, bool, error) {
func ParseTimeExactMatchLegacy(layout, potentialDateTimeString string) (time.Time, bool, error) {
ts, err := time.Parse(layout, potentialDateTimeString)
if err != nil {
return ts, false, err
Expand All @@ -51,7 +66,7 @@ func ParseExtendedDateTime(dtString string, additionalDateFormats []string) (*Ex
var potentialFormat string
var potentialTime time.Time
for _, supportedDateTimeLayout := range supportedDateTimeLayouts {
ts, exactMatch, err := ParseTimeExactMatch(supportedDateTimeLayout, dtString)
ts, exactMatch, err := ParseTimeExactMatchLegacy(supportedDateTimeLayout, dtString)
if err == nil {
potentialFormat = supportedDateTimeLayout
potentialTime = ts
Expand All @@ -63,18 +78,17 @@ func ParseExtendedDateTime(dtString string, additionalDateFormats []string) (*Ex

// Now check DATE formats, btw you can append nil arrays
for _, supportedDateFormat := range append(supportedDateFormats, additionalDateFormats...) {
ts, exactMatch, err := ParseTimeExactMatch(supportedDateFormat, dtString)
ts, exactMatch, err := ParseTimeExactMatchLegacy(supportedDateFormat, dtString)
if err == nil && exactMatch {
return NewExtendedTime(ts, DateKindType, supportedDateFormat), nil
}
}

// Now check TIME formats
for _, supportedTimeFormat := range supportedTimeFormats {
ts, exactMatch, err := ParseTimeExactMatch(supportedTimeFormat, dtString)
for _, supportedTimeFormat := range SupportedTimeFormatsLegacy {
ts, exactMatch, err := ParseTimeExactMatchLegacy(supportedTimeFormat, dtString)
if err == nil && exactMatch {
return NewExtendedTime(ts, TimeKindType, supportedTimeFormat), nil

}
}

Expand Down
4 changes: 2 additions & 2 deletions lib/typing/ext/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func TestParseFromInterfaceDateTime(t *testing.T) {

func TestParseFromInterfaceTime(t *testing.T) {
now := time.Now()
for _, supportedTimeFormat := range supportedTimeFormats {
for _, supportedTimeFormat := range SupportedTimeFormatsLegacy {
et, err := ParseFromInterface(now.Format(supportedTimeFormat), nil)
assert.NoError(t, err)
assert.Equal(t, et.NestedKind.Type, TimeKindType)
Expand Down Expand Up @@ -110,7 +110,7 @@ func TestParseExtendedDateTime(t *testing.T) {
func TestTimeLayout(t *testing.T) {
ts := time.Now()

for _, supportedFormat := range supportedTimeFormats {
for _, supportedFormat := range SupportedTimeFormatsLegacy {
parsedTsString := ts.Format(supportedFormat)
extTime, err := ParseExtendedDateTime(parsedTsString, nil)
assert.NoError(t, err)
Expand Down
8 changes: 0 additions & 8 deletions lib/typing/ext/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,3 @@ func (e *ExtendedTime) String(overrideFormat string) string {

return e.Time.Format(e.NestedKind.Format)
}

func (e *ExtendedTime) StringUTC(overrideFormat string) string {
if overrideFormat != "" {
return e.Time.In(time.UTC).Format(overrideFormat)
}

return e.Time.In(time.UTC).Format(e.NestedKind.Format)
}
16 changes: 15 additions & 1 deletion lib/typing/ext/variables.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,26 @@ var supportedDateFormats = []string{
PostgresDateFormat,
}

var supportedTimeFormats = []string{
// TODO: Remove
var SupportedTimeFormatsLegacy = []string{
PostgresTimeFormat,
PostgresTimeFormatNoTZ,
AdditionalTimeFormat,
}

var SupportedTimeWithTimezoneFormats = []string{
// Debezium will emit `Z` for UTC time
"15:04:05Z", // w/o fractional seconds
"15:04:05.000Z", // ms
"15:04:05.000000Z", // microseconds

// TODO: Get Reader to be consistent with Debezium
// Reader will omit ts locale
"15:04:05", // w/o fractional seconds
"15:04:05.000", // ms
"15:04:05.000000", // microseconds
}

func NewUTCTime(layout string) string {
return time.Now().UTC().Format(layout)
}

0 comments on commit 91ca180

Please sign in to comment.