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.
destAddr := test.GetDestAddr(t, 0)
faultyTime, err := parseSqliteTimeStamp("55563-06-27 02:09:24 +0000 UTC")
require.NoError(t, err)
// Corresponds to 55563-06-27 02:09:24 +0000 UTC.
faultyTime := time.Unix(1691247002964, 0)
t.Log(faultyTime.Unix())
unrestrictedSwap := LoopOutContract{
SwapContract: SwapContract{
@ -362,7 +364,7 @@ func TestIssue615(t *testing.T) {
SwapPublicationDeadline: faultyTime,
}
err = sqlDB.CreateLoopOut(ctxb, testPreimage.Hash(), &unrestrictedSwap)
err := sqlDB.CreateLoopOut(ctxb, testPreimage.Hash(), &unrestrictedSwap)
require.NoError(t, err)
// 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",
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",
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",
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",
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 {
time, err := parseTimeStamp(test.timeString)
time, err := fixTimeStamp(test.timeString)
require.NoError(t, err)
require.Equal(t, test.expectedTime, time)
}

@ -3,6 +3,7 @@ package loopdb
import (
"context"
"database/sql"
"errors"
"fmt"
"net/url"
"path/filepath"
@ -247,19 +248,25 @@ func (b *BaseDB) FixFaultyTimestamps(ctx context.Context) error {
defer tx.Rollback() //nolint: errcheck
for _, swap := range loopOutSwaps {
faultyTime, err := parseTimeStamp(swap.PublicationDeadline)
// Get the year of the timestamp.
year, err := getTimeStampYear(swap.PublicationDeadline)
if err != nil {
return err
}
// Skip if the time is not faulty.
if !isMilisecondsTime(faultyTime.Unix()) {
// Skip if the year is not in the future.
thisYear := time.Now().Year()
if year <= thisYear {
continue
}
fixedTime, err := fixTimeStamp(swap.PublicationDeadline)
if err != nil {
return err
}
// Update the faulty time to a valid time.
secs := faultyTime.Unix() / 1000
correctTime := time.Unix(secs, 0)
_, err = tx.ExecContext(
ctx, `
UPDATE
@ -269,7 +276,7 @@ func (b *BaseDB) FixFaultyTimestamps(ctx context.Context) error {
WHERE
swap_hash = $2;
`,
correctTime, swap.Hash,
fixedTime, swap.Hash,
)
if err != nil {
return err
@ -308,120 +315,84 @@ func (r *SqliteTxOptions) ReadOnly() bool {
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.
// If both fail, it returns an error.
func parseTimeStamp(dateTimeStr string) (time.Time, error) {
t, err := parseSqliteTimeStamp(dateTimeStr)
func fixTimeStamp(dateTimeStr string) (time.Time, error) {
year, err := getTimeStampYear(dateTimeStr)
if err != nil {
t, err = parsePostgresTimeStamp(dateTimeStr)
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)
return time.Time{}, err
}
datePart, timePart := parts[0], strings.TrimSuffix(parts[1], "Z")
return parseTimeParts(datePart, timePart)
}
// parseTimeParts takes a datePart string in the format of "YYYY-MM-DD" and
// a timePart string in the format of "HH:MM:SS" and returns a time.Time value.
func parseTimeParts(datePart, timePart string) (time.Time, error) {
// Parse the date.
dateParts := strings.Split(datePart, "-")
if len(dateParts) != 3 {
return time.Time{}, fmt.Errorf("invalid date format: %v",
datePart)
// If the year is in the future. It was a faulty timestamp.
thisYear := time.Now().Year()
if year > thisYear {
dateTimeStr = strings.Replace(
dateTimeStr,
fmt.Sprintf("%d", year),
fmt.Sprintf("%d", thisYear),
1,
)
}
year, err := strconv.Atoi(dateParts[0])
parsedTime, err := parseLayouts(defaultLayouts(), dateTimeStr)
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])
if err != nil {
return time.Time{}, err
}
return parsedTime.UTC(), nil
}
day, err := strconv.Atoi(dateParts[2])
if err != nil {
return time.Time{}, err
// parseLayouts parses time based on a list of provided layouts.
// If layouts is empty list or nil, the error with unknown layout will be returned.
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.
timeParts := strings.Split(timePart, ":")
if len(timeParts) != 3 {
return time.Time{}, fmt.Errorf("invalid time format: %v",
timePart)
}
return time.Time{}, errors.New("unknown layout")
}
hour, err := strconv.Atoi(timeParts[0])
if err != nil {
return time.Time{}, err
// defaultLayouts returns a default list of ALL supported layouts.
// This function returns new copy of a slice.
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])
if err != nil {
return time.Time{}, err
// getTimeStampYear returns the year of a timestamp string.
func getTimeStampYear(dateTimeStr string) (int, error) {
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.
secondParts := strings.Split(timeParts[2], ".")
second, err := strconv.Atoi(secondParts[0])
year, err := strconv.Atoi(parts[0])
if err != nil {
return time.Time{}, err
return 0, fmt.Errorf("unable to parse year: %v", err)
}
// Construct a time.Time value.
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
}
return year, nil
}

Loading…
Cancel
Save