Skip to content

Commit cf59c48

Browse files
authored
Merge pull request #630 from sputn1ck/timeparsing_fix
sqldb: fix timeparsing edge cases
2 parents 1f48b2c + 942b0dc commit cf59c48

File tree

2 files changed

+106
-109
lines changed

2 files changed

+106
-109
lines changed

loopdb/sql_test.go

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -328,8 +328,10 @@ func TestIssue615(t *testing.T) {
328328

329329
// Create a faulty loopout swap.
330330
destAddr := test.GetDestAddr(t, 0)
331-
faultyTime, err := parseSqliteTimeStamp("55563-06-27 02:09:24 +0000 UTC")
332-
require.NoError(t, err)
331+
// Corresponds to 55563-06-27 02:09:24 +0000 UTC.
332+
faultyTime := time.Unix(1691247002964, 0)
333+
334+
t.Log(faultyTime.Unix())
333335

334336
unrestrictedSwap := LoopOutContract{
335337
SwapContract: SwapContract{
@@ -362,7 +364,7 @@ func TestIssue615(t *testing.T) {
362364
SwapPublicationDeadline: faultyTime,
363365
}
364366

365-
err = sqlDB.CreateLoopOut(ctxb, testPreimage.Hash(), &unrestrictedSwap)
367+
err := sqlDB.CreateLoopOut(ctxb, testPreimage.Hash(), &unrestrictedSwap)
366368
require.NoError(t, err)
367369

368370
// This should fail because of the faulty timestamp.
@@ -394,30 +396,54 @@ func TestTimeConversions(t *testing.T) {
394396
},
395397
{
396398
timeString: "2018-11-01 00:00:01.10000 +0000 UTC",
397-
expectedTime: time.Date(2018, 11, 1, 0, 0, 1, 0, time.UTC),
399+
expectedTime: time.Date(2018, 11, 1, 0, 0, 1, 100000000, time.UTC),
398400
},
399401
{
400402
timeString: "2053-12-29T02:40:44.269009408Z",
401403
expectedTime: time.Date(
402-
2053, 12, 29, 2, 40, 44, 0, time.UTC,
404+
time.Now().Year(), 12, 29, 2, 40, 44, 269009408, time.UTC,
403405
),
404406
},
405407
{
406408
timeString: "55563-06-27 02:09:24 +0000 UTC",
407409
expectedTime: time.Date(
408-
55563, 6, 27, 2, 9, 24, 0, time.UTC,
410+
time.Now().Year(), 6, 27, 2, 9, 24, 0, time.UTC,
409411
),
410412
},
411413
{
412414
timeString: "2172-03-11 10:01:11.849906176 +0000 UTC",
413415
expectedTime: time.Date(
414-
2172, 3, 11, 10, 1, 11, 0, time.UTC,
416+
time.Now().Year(), 3, 11, 10, 1, 11, 849906176, time.UTC,
417+
),
418+
},
419+
{
420+
timeString: "2023-08-04 16:07:49 +0800 CST",
421+
expectedTime: time.Date(
422+
2023, 8, 4, 8, 7, 49, 0, time.UTC,
423+
),
424+
},
425+
{
426+
timeString: "2023-08-04 16:07:49 -0700 MST",
427+
expectedTime: time.Date(
428+
2023, 8, 4, 23, 7, 49, 0, time.UTC,
429+
),
430+
},
431+
{
432+
timeString: "2023-08-04T16:07:49+08:00",
433+
expectedTime: time.Date(
434+
2023, 8, 4, 8, 7, 49, 0, time.UTC,
435+
),
436+
},
437+
{
438+
timeString: "2023-08-04T16:07:49+08:00",
439+
expectedTime: time.Date(
440+
2023, 8, 4, 8, 7, 49, 0, time.UTC,
415441
),
416442
},
417443
}
418444

419445
for _, test := range tests {
420-
time, err := parseTimeStamp(test.timeString)
446+
time, err := fixTimeStamp(test.timeString)
421447
require.NoError(t, err)
422448
require.Equal(t, test.expectedTime, time)
423449
}

loopdb/sqlite.go

Lines changed: 72 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package loopdb
33
import (
44
"context"
55
"database/sql"
6+
"errors"
67
"fmt"
78
"net/url"
89
"path/filepath"
@@ -247,19 +248,25 @@ func (b *BaseDB) FixFaultyTimestamps(ctx context.Context) error {
247248
defer tx.Rollback() //nolint: errcheck
248249

249250
for _, swap := range loopOutSwaps {
250-
faultyTime, err := parseTimeStamp(swap.PublicationDeadline)
251+
252+
// Get the year of the timestamp.
253+
year, err := getTimeStampYear(swap.PublicationDeadline)
251254
if err != nil {
252255
return err
253256
}
254257

255-
// Skip if the time is not faulty.
256-
if !isMilisecondsTime(faultyTime.Unix()) {
258+
// Skip if the year is not in the future.
259+
thisYear := time.Now().Year()
260+
if year <= thisYear {
257261
continue
258262
}
259263

264+
fixedTime, err := fixTimeStamp(swap.PublicationDeadline)
265+
if err != nil {
266+
return err
267+
}
268+
260269
// Update the faulty time to a valid time.
261-
secs := faultyTime.Unix() / 1000
262-
correctTime := time.Unix(secs, 0)
263270
_, err = tx.ExecContext(
264271
ctx, `
265272
UPDATE
@@ -269,7 +276,7 @@ func (b *BaseDB) FixFaultyTimestamps(ctx context.Context) error {
269276
WHERE
270277
swap_hash = $2;
271278
`,
272-
correctTime, swap.Hash,
279+
fixedTime, swap.Hash,
273280
)
274281
if err != nil {
275282
return err
@@ -308,120 +315,84 @@ func (r *SqliteTxOptions) ReadOnly() bool {
308315
return r.readOnly
309316
}
310317

311-
// parseTimeStamp tries to parse a timestamp string with both the
318+
// fixTimeStamp tries to parse a timestamp string with both the
312319
// parseSqliteTimeStamp and parsePostgresTimeStamp functions.
313320
// If both fail, it returns an error.
314-
func parseTimeStamp(dateTimeStr string) (time.Time, error) {
315-
t, err := parseSqliteTimeStamp(dateTimeStr)
321+
func fixTimeStamp(dateTimeStr string) (time.Time, error) {
322+
year, err := getTimeStampYear(dateTimeStr)
316323
if err != nil {
317-
t, err = parsePostgresTimeStamp(dateTimeStr)
318-
if err != nil {
319-
return time.Time{}, err
320-
}
321-
}
322-
323-
return t, nil
324-
}
325-
326-
// parseSqliteTimeStamp parses a timestamp string in the format of
327-
// "YYYY-MM-DD HH:MM:SS +0000 UTC" and returns a time.Time value.
328-
// NOTE: we can't use time.Parse() because it doesn't support having years
329-
// with more than 4 digits.
330-
func parseSqliteTimeStamp(dateTimeStr string) (time.Time, error) {
331-
// Split the date and time parts.
332-
parts := strings.Fields(strings.TrimSpace(dateTimeStr))
333-
if len(parts) < 2 {
334-
return time.Time{}, fmt.Errorf("invalid timestamp format: %v",
335-
dateTimeStr)
336-
}
337-
338-
datePart, timePart := parts[0], parts[1]
339-
340-
return parseTimeParts(datePart, timePart)
341-
}
342-
343-
// parseSqliteTimeStamp parses a timestamp string in the format of
344-
// "YYYY-MM-DDTHH:MM:SSZ" and returns a time.Time value.
345-
// NOTE: we can't use time.Parse() because it doesn't support having years
346-
// with more than 4 digits.
347-
func parsePostgresTimeStamp(dateTimeStr string) (time.Time, error) {
348-
// Split the date and time parts.
349-
parts := strings.Split(dateTimeStr, "T")
350-
if len(parts) != 2 {
351-
return time.Time{}, fmt.Errorf("invalid timestamp format: %v",
352-
dateTimeStr)
324+
return time.Time{}, err
353325
}
354326

355-
datePart, timePart := parts[0], strings.TrimSuffix(parts[1], "Z")
356-
357-
return parseTimeParts(datePart, timePart)
358-
}
359-
360-
// parseTimeParts takes a datePart string in the format of "YYYY-MM-DD" and
361-
// a timePart string in the format of "HH:MM:SS" and returns a time.Time value.
362-
func parseTimeParts(datePart, timePart string) (time.Time, error) {
363-
// Parse the date.
364-
dateParts := strings.Split(datePart, "-")
365-
if len(dateParts) != 3 {
366-
return time.Time{}, fmt.Errorf("invalid date format: %v",
367-
datePart)
327+
// If the year is in the future. It was a faulty timestamp.
328+
thisYear := time.Now().Year()
329+
if year > thisYear {
330+
dateTimeStr = strings.Replace(
331+
dateTimeStr,
332+
fmt.Sprintf("%d", year),
333+
fmt.Sprintf("%d", thisYear),
334+
1,
335+
)
368336
}
369337

370-
year, err := strconv.Atoi(dateParts[0])
338+
parsedTime, err := parseLayouts(defaultLayouts(), dateTimeStr)
371339
if err != nil {
372-
return time.Time{}, err
340+
return time.Time{}, fmt.Errorf("unable to parse timestamp %v: %v",
341+
dateTimeStr, err)
373342
}
374343

375-
month, err := strconv.Atoi(dateParts[1])
376-
if err != nil {
377-
return time.Time{}, err
378-
}
344+
return parsedTime.UTC(), nil
345+
}
379346

380-
day, err := strconv.Atoi(dateParts[2])
381-
if err != nil {
382-
return time.Time{}, err
347+
// parseLayouts parses time based on a list of provided layouts.
348+
// If layouts is empty list or nil, the error with unknown layout will be returned.
349+
func parseLayouts(layouts []string, dateTime string) (time.Time, error) {
350+
for _, layout := range layouts {
351+
parsedTime, err := time.Parse(layout, dateTime)
352+
if err == nil {
353+
return parsedTime, nil
354+
}
383355
}
384356

385-
// Parse the time.
386-
timeParts := strings.Split(timePart, ":")
387-
if len(timeParts) != 3 {
388-
return time.Time{}, fmt.Errorf("invalid time format: %v",
389-
timePart)
390-
}
357+
return time.Time{}, errors.New("unknown layout")
358+
}
391359

392-
hour, err := strconv.Atoi(timeParts[0])
393-
if err != nil {
394-
return time.Time{}, err
360+
// defaultLayouts returns a default list of ALL supported layouts.
361+
// This function returns new copy of a slice.
362+
func defaultLayouts() []string {
363+
return []string{
364+
"2006-01-02 15:04:05.99999 -0700 MST", // Custom sqlite layout.
365+
time.RFC3339Nano,
366+
time.RFC3339,
367+
time.RFC1123Z,
368+
time.RFC1123,
369+
time.RFC850,
370+
time.RFC822Z,
371+
time.RFC822,
372+
time.Layout,
373+
time.RubyDate,
374+
time.UnixDate,
375+
time.ANSIC,
376+
time.StampNano,
377+
time.StampMicro,
378+
time.StampMilli,
379+
time.Stamp,
380+
time.Kitchen,
395381
}
382+
}
396383

397-
minute, err := strconv.Atoi(timeParts[1])
398-
if err != nil {
399-
return time.Time{}, err
384+
// getTimeStampYear returns the year of a timestamp string.
385+
func getTimeStampYear(dateTimeStr string) (int, error) {
386+
parts := strings.Split(dateTimeStr, "-")
387+
if len(parts) < 1 {
388+
return 0, fmt.Errorf("invalid timestamp format: %v",
389+
dateTimeStr)
400390
}
401391

402-
// Parse the seconds and ignore the fractional part.
403-
secondParts := strings.Split(timeParts[2], ".")
404-
405-
second, err := strconv.Atoi(secondParts[0])
392+
year, err := strconv.Atoi(parts[0])
406393
if err != nil {
407-
return time.Time{}, err
394+
return 0, fmt.Errorf("unable to parse year: %v", err)
408395
}
409396

410-
// Construct a time.Time value.
411-
return time.Date(
412-
year, time.Month(month), day, hour, minute, second, 0, time.UTC,
413-
), nil
414-
}
415-
416-
// isMilisecondsTime returns true if the unix timestamp is likely in
417-
// milliseconds.
418-
func isMilisecondsTime(unixTimestamp int64) bool {
419-
length := len(fmt.Sprintf("%d", unixTimestamp))
420-
if length >= 13 {
421-
// Likely a millisecond timestamp
422-
return true
423-
} else {
424-
// Likely a second timestamp
425-
return false
426-
}
397+
return year, nil
427398
}

0 commit comments

Comments
 (0)