Merge pull request #630 from sputn1ck/timeparsing_fix

sqldb: fix timeparsing edge cases
pull/635/head
Konstantin Nick 9 months ago committed by GitHub
commit cf59c4856f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -328,8 +328,10 @@ func TestIssue615(t *testing.T) {
// Create a faulty loopout swap. // Create a faulty loopout swap.
destAddr := test.GetDestAddr(t, 0) destAddr := test.GetDestAddr(t, 0)
faultyTime, err := parseSqliteTimeStamp("55563-06-27 02:09:24 +0000 UTC") // Corresponds to 55563-06-27 02:09:24 +0000 UTC.
require.NoError(t, err) faultyTime := time.Unix(1691247002964, 0)
t.Log(faultyTime.Unix())
unrestrictedSwap := LoopOutContract{ unrestrictedSwap := LoopOutContract{
SwapContract: SwapContract{ SwapContract: SwapContract{
@ -362,7 +364,7 @@ func TestIssue615(t *testing.T) {
SwapPublicationDeadline: faultyTime, SwapPublicationDeadline: faultyTime,
} }
err = sqlDB.CreateLoopOut(ctxb, testPreimage.Hash(), &unrestrictedSwap) err := sqlDB.CreateLoopOut(ctxb, testPreimage.Hash(), &unrestrictedSwap)
require.NoError(t, err) require.NoError(t, err)
// This should fail because of the faulty timestamp. // This should fail because of the faulty timestamp.
@ -394,30 +396,54 @@ func TestTimeConversions(t *testing.T) {
}, },
{ {
timeString: "2018-11-01 00:00:01.10000 +0000 UTC", timeString: "2018-11-01 00:00:01.10000 +0000 UTC",
expectedTime: time.Date(2018, 11, 1, 0, 0, 1, 0, time.UTC), expectedTime: time.Date(2018, 11, 1, 0, 0, 1, 100000000, time.UTC),
}, },
{ {
timeString: "2053-12-29T02:40:44.269009408Z", timeString: "2053-12-29T02:40:44.269009408Z",
expectedTime: time.Date( expectedTime: time.Date(
2053, 12, 29, 2, 40, 44, 0, time.UTC, time.Now().Year(), 12, 29, 2, 40, 44, 269009408, time.UTC,
), ),
}, },
{ {
timeString: "55563-06-27 02:09:24 +0000 UTC", timeString: "55563-06-27 02:09:24 +0000 UTC",
expectedTime: time.Date( expectedTime: time.Date(
55563, 6, 27, 2, 9, 24, 0, time.UTC, time.Now().Year(), 6, 27, 2, 9, 24, 0, time.UTC,
), ),
}, },
{ {
timeString: "2172-03-11 10:01:11.849906176 +0000 UTC", timeString: "2172-03-11 10:01:11.849906176 +0000 UTC",
expectedTime: time.Date( expectedTime: time.Date(
2172, 3, 11, 10, 1, 11, 0, time.UTC, time.Now().Year(), 3, 11, 10, 1, 11, 849906176, time.UTC,
),
},
{
timeString: "2023-08-04 16:07:49 +0800 CST",
expectedTime: time.Date(
2023, 8, 4, 8, 7, 49, 0, time.UTC,
),
},
{
timeString: "2023-08-04 16:07:49 -0700 MST",
expectedTime: time.Date(
2023, 8, 4, 23, 7, 49, 0, time.UTC,
),
},
{
timeString: "2023-08-04T16:07:49+08:00",
expectedTime: time.Date(
2023, 8, 4, 8, 7, 49, 0, time.UTC,
),
},
{
timeString: "2023-08-04T16:07:49+08:00",
expectedTime: time.Date(
2023, 8, 4, 8, 7, 49, 0, time.UTC,
), ),
}, },
} }
for _, test := range tests { for _, test := range tests {
time, err := parseTimeStamp(test.timeString) time, err := fixTimeStamp(test.timeString)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, test.expectedTime, time) require.Equal(t, test.expectedTime, time)
} }

@ -3,6 +3,7 @@ package loopdb
import ( import (
"context" "context"
"database/sql" "database/sql"
"errors"
"fmt" "fmt"
"net/url" "net/url"
"path/filepath" "path/filepath"
@ -247,19 +248,25 @@ func (b *BaseDB) FixFaultyTimestamps(ctx context.Context) error {
defer tx.Rollback() //nolint: errcheck defer tx.Rollback() //nolint: errcheck
for _, swap := range loopOutSwaps { for _, swap := range loopOutSwaps {
faultyTime, err := parseTimeStamp(swap.PublicationDeadline)
// Get the year of the timestamp.
year, err := getTimeStampYear(swap.PublicationDeadline)
if err != nil { if err != nil {
return err return err
} }
// Skip if the time is not faulty. // Skip if the year is not in the future.
if !isMilisecondsTime(faultyTime.Unix()) { thisYear := time.Now().Year()
if year <= thisYear {
continue continue
} }
fixedTime, err := fixTimeStamp(swap.PublicationDeadline)
if err != nil {
return err
}
// Update the faulty time to a valid time. // Update the faulty time to a valid time.
secs := faultyTime.Unix() / 1000
correctTime := time.Unix(secs, 0)
_, err = tx.ExecContext( _, err = tx.ExecContext(
ctx, ` ctx, `
UPDATE UPDATE
@ -269,7 +276,7 @@ func (b *BaseDB) FixFaultyTimestamps(ctx context.Context) error {
WHERE WHERE
swap_hash = $2; swap_hash = $2;
`, `,
correctTime, swap.Hash, fixedTime, swap.Hash,
) )
if err != nil { if err != nil {
return err return err
@ -308,120 +315,84 @@ func (r *SqliteTxOptions) ReadOnly() bool {
return r.readOnly return r.readOnly
} }
// parseTimeStamp tries to parse a timestamp string with both the // fixTimeStamp tries to parse a timestamp string with both the
// parseSqliteTimeStamp and parsePostgresTimeStamp functions. // parseSqliteTimeStamp and parsePostgresTimeStamp functions.
// If both fail, it returns an error. // If both fail, it returns an error.
func parseTimeStamp(dateTimeStr string) (time.Time, error) { func fixTimeStamp(dateTimeStr string) (time.Time, error) {
t, err := parseSqliteTimeStamp(dateTimeStr) year, err := getTimeStampYear(dateTimeStr)
if err != nil { if err != nil {
t, err = parsePostgresTimeStamp(dateTimeStr) return time.Time{}, err
if err != nil {
return time.Time{}, err
}
}
return t, nil
}
// parseSqliteTimeStamp parses a timestamp string in the format of
// "YYYY-MM-DD HH:MM:SS +0000 UTC" and returns a time.Time value.
// NOTE: we can't use time.Parse() because it doesn't support having years
// with more than 4 digits.
func parseSqliteTimeStamp(dateTimeStr string) (time.Time, error) {
// Split the date and time parts.
parts := strings.Fields(strings.TrimSpace(dateTimeStr))
if len(parts) < 2 {
return time.Time{}, fmt.Errorf("invalid timestamp format: %v",
dateTimeStr)
}
datePart, timePart := parts[0], parts[1]
return parseTimeParts(datePart, timePart)
}
// parseSqliteTimeStamp parses a timestamp string in the format of
// "YYYY-MM-DDTHH:MM:SSZ" and returns a time.Time value.
// NOTE: we can't use time.Parse() because it doesn't support having years
// with more than 4 digits.
func parsePostgresTimeStamp(dateTimeStr string) (time.Time, error) {
// Split the date and time parts.
parts := strings.Split(dateTimeStr, "T")
if len(parts) != 2 {
return time.Time{}, fmt.Errorf("invalid timestamp format: %v",
dateTimeStr)
} }
datePart, timePart := parts[0], strings.TrimSuffix(parts[1], "Z") // If the year is in the future. It was a faulty timestamp.
thisYear := time.Now().Year()
return parseTimeParts(datePart, timePart) if year > thisYear {
} dateTimeStr = strings.Replace(
dateTimeStr,
// parseTimeParts takes a datePart string in the format of "YYYY-MM-DD" and fmt.Sprintf("%d", year),
// a timePart string in the format of "HH:MM:SS" and returns a time.Time value. fmt.Sprintf("%d", thisYear),
func parseTimeParts(datePart, timePart string) (time.Time, error) { 1,
// Parse the date. )
dateParts := strings.Split(datePart, "-")
if len(dateParts) != 3 {
return time.Time{}, fmt.Errorf("invalid date format: %v",
datePart)
} }
year, err := strconv.Atoi(dateParts[0]) parsedTime, err := parseLayouts(defaultLayouts(), dateTimeStr)
if err != nil { if err != nil {
return time.Time{}, err return time.Time{}, fmt.Errorf("unable to parse timestamp %v: %v",
dateTimeStr, err)
} }
month, err := strconv.Atoi(dateParts[1]) return parsedTime.UTC(), nil
if err != nil { }
return time.Time{}, err
}
day, err := strconv.Atoi(dateParts[2]) // parseLayouts parses time based on a list of provided layouts.
if err != nil { // If layouts is empty list or nil, the error with unknown layout will be returned.
return time.Time{}, err func parseLayouts(layouts []string, dateTime string) (time.Time, error) {
for _, layout := range layouts {
parsedTime, err := time.Parse(layout, dateTime)
if err == nil {
return parsedTime, nil
}
} }
// Parse the time. return time.Time{}, errors.New("unknown layout")
timeParts := strings.Split(timePart, ":") }
if len(timeParts) != 3 {
return time.Time{}, fmt.Errorf("invalid time format: %v",
timePart)
}
hour, err := strconv.Atoi(timeParts[0]) // defaultLayouts returns a default list of ALL supported layouts.
if err != nil { // This function returns new copy of a slice.
return time.Time{}, err func defaultLayouts() []string {
return []string{
"2006-01-02 15:04:05.99999 -0700 MST", // Custom sqlite layout.
time.RFC3339Nano,
time.RFC3339,
time.RFC1123Z,
time.RFC1123,
time.RFC850,
time.RFC822Z,
time.RFC822,
time.Layout,
time.RubyDate,
time.UnixDate,
time.ANSIC,
time.StampNano,
time.StampMicro,
time.StampMilli,
time.Stamp,
time.Kitchen,
} }
}
minute, err := strconv.Atoi(timeParts[1]) // getTimeStampYear returns the year of a timestamp string.
if err != nil { func getTimeStampYear(dateTimeStr string) (int, error) {
return time.Time{}, err parts := strings.Split(dateTimeStr, "-")
if len(parts) < 1 {
return 0, fmt.Errorf("invalid timestamp format: %v",
dateTimeStr)
} }
// Parse the seconds and ignore the fractional part. year, err := strconv.Atoi(parts[0])
secondParts := strings.Split(timeParts[2], ".")
second, err := strconv.Atoi(secondParts[0])
if err != nil { if err != nil {
return time.Time{}, err return 0, fmt.Errorf("unable to parse year: %v", err)
} }
// Construct a time.Time value. return year, nil
return time.Date(
year, time.Month(month), day, hour, minute, second, 0, time.UTC,
), nil
}
// isMilisecondsTime returns true if the unix timestamp is likely in
// milliseconds.
func isMilisecondsTime(unixTimestamp int64) bool {
length := len(fmt.Sprintf("%d", unixTimestamp))
if length >= 13 {
// Likely a millisecond timestamp
return true
} else {
// Likely a second timestamp
return false
}
} }

Loading…
Cancel
Save