Skip to content

Commit 64bdef9

Browse files
committed
sqldb: change time parsing function
This commit changes the time parsing function to use the time.Parse function instead of the prior manual parsing. Only if the year is far in the future, we replace it with the current year.
1 parent 1f48b2c commit 64bdef9

File tree

2 files changed

+87
-98
lines changed

2 files changed

+87
-98
lines changed

loopdb/sql_test.go

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -394,24 +394,48 @@ func TestTimeConversions(t *testing.T) {
394394
},
395395
{
396396
timeString: "2018-11-01 00:00:01.10000 +0000 UTC",
397-
expectedTime: time.Date(2018, 11, 1, 0, 0, 1, 0, time.UTC),
397+
expectedTime: time.Date(2018, 11, 1, 0, 0, 1, 100000000, time.UTC),
398398
},
399399
{
400400
timeString: "2053-12-29T02:40:44.269009408Z",
401401
expectedTime: time.Date(
402-
2053, 12, 29, 2, 40, 44, 0, time.UTC,
402+
time.Now().Year(), 12, 29, 2, 40, 44, 269009408, time.UTC,
403403
),
404404
},
405405
{
406406
timeString: "55563-06-27 02:09:24 +0000 UTC",
407407
expectedTime: time.Date(
408-
55563, 6, 27, 2, 9, 24, 0, time.UTC,
408+
time.Now().Year(), 6, 27, 2, 9, 24, 0, time.UTC,
409409
),
410410
},
411411
{
412412
timeString: "2172-03-11 10:01:11.849906176 +0000 UTC",
413413
expectedTime: time.Date(
414-
2172, 3, 11, 10, 1, 11, 0, time.UTC,
414+
time.Now().Year(), 3, 11, 10, 1, 11, 849906176, time.UTC,
415+
),
416+
},
417+
{
418+
timeString: "2023-08-04 16:07:49 +0800 CST",
419+
expectedTime: time.Date(
420+
2023, 8, 4, 8, 7, 49, 0, time.UTC,
421+
),
422+
},
423+
{
424+
timeString: "2023-08-04 16:07:49 -0700 MST",
425+
expectedTime: time.Date(
426+
2023, 8, 4, 23, 7, 49, 0, time.UTC,
427+
),
428+
},
429+
{
430+
timeString: "2023-08-04T16:07:49+08:00",
431+
expectedTime: time.Date(
432+
2023, 8, 4, 8, 7, 49, 0, time.UTC,
433+
),
434+
},
435+
{
436+
timeString: "2023-08-04T16:07:49+08:00",
437+
expectedTime: time.Date(
438+
2023, 8, 4, 8, 7, 49, 0, time.UTC,
415439
),
416440
},
417441
}

loopdb/sqlite.go

Lines changed: 59 additions & 94 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"
@@ -311,117 +312,81 @@ func (r *SqliteTxOptions) ReadOnly() bool {
311312
// parseTimeStamp tries to parse a timestamp string with both the
312313
// parseSqliteTimeStamp and parsePostgresTimeStamp functions.
313314
// If both fail, it returns an error.
314-
func parseTimeStamp(dateTimeStr string) (time.Time, error) {
315-
t, err := parseSqliteTimeStamp(dateTimeStr)
315+
func fixTimeStamp(dateTimeStr string) (time.Time, error) {
316+
year, err := getTimeStampYear(dateTimeStr)
316317
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)
318+
return time.Time{}, err
353319
}
354320

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)
321+
// If the year is in the future. It was a faulty timestamp.
322+
thisYear := time.Now().Year()
323+
if year > thisYear {
324+
dateTimeStr = strings.Replace(
325+
dateTimeStr,
326+
fmt.Sprintf("%d", year),
327+
fmt.Sprintf("%d", thisYear),
328+
1,
329+
)
368330
}
369331

370-
year, err := strconv.Atoi(dateParts[0])
332+
parsedTime, err := parseLayouts(defaultLayouts(), dateTimeStr)
371333
if err != nil {
372-
return time.Time{}, err
334+
return time.Time{}, fmt.Errorf("unable to parse timestamp %v: %v",
335+
dateTimeStr, err)
373336
}
374337

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

380-
day, err := strconv.Atoi(dateParts[2])
381-
if err != nil {
382-
return time.Time{}, err
341+
// parseLayouts parses time based on a list of provided layouts.
342+
// If layouts is empty list or nil, the error with unknown layout will be returned.
343+
func parseLayouts(layouts []string, dateTime string) (time.Time, error) {
344+
for _, layout := range layouts {
345+
parsedTime, err := time.Parse(layout, dateTime)
346+
if err == nil {
347+
return parsedTime, nil
348+
}
383349
}
384350

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-
}
351+
return time.Time{}, errors.New("unknown layout")
352+
}
391353

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

397-
minute, err := strconv.Atoi(timeParts[1])
398-
if err != nil {
399-
return time.Time{}, err
378+
// getTimeStampYear returns the year of a timestamp string.
379+
func getTimeStampYear(dateTimeStr string) (int, error) {
380+
parts := strings.Split(dateTimeStr, "-")
381+
if len(parts) < 1 {
382+
return 0, fmt.Errorf("invalid timestamp format: %v",
383+
dateTimeStr)
400384
}
401385

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

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-
}
391+
return year, nil
427392
}

0 commit comments

Comments
 (0)