How can I parse a time in Go with no period separating seconds and milliseconds?

Viewed 204

I'm having to parse times that are in the format:

20060102150405000

E.g. "20170116111621978" represents 16 January 2017, 11:16:21.978. It looks to me like the Go time package requires an embedded period in the date format (05.000) (and hence the input). I can't think of any way short of rewriting the source string to include a period to parse these with time.Parse. Anyone have a suggestion?

3 Answers

Unfortunately the time package requires a period prefix for the fraction seconds part (milliseconds) in the layout, so you have 2 choices:

  • either modify the input to insert a period, so you can use the layout 20060102150405.000
  • or cut off the last 3 digits, parse the prefix using the layout 20060102150405, parse the milliseconds (e.g. using strconv.Atoi()) and add it to the time using Time.Add().

This seems to do it:

package main

import (
   "fmt"
   "time"
)

func main() {
   var (
      year, day, hour, min, sec, mil int
      mon time.Month
   )
   fmt.Sscanf(
      "20060102150405000", "%4v%2v%2v%2v%2v%2v%3v",
      &year, &mon, &day, &hour, &min, &sec, &mil,
   )
   t := time.Date(year, mon, day, hour, min, sec, mil * 1e6, time.UTC)
   fmt.Println(t) // 2006-01-02 15:04:05 +0000 UTC
}

https://golang.org/pkg/fmt#Sscanf

It seems there's no way to do this with the standard Go parse format.

A trick to parse the date without error is to append to the format (up to the seconds) the exact last three millisecond digits time.Parse("20060102150405"+t[len(t)-3:], t) but this only ensures the parsing doesn't return error. It will lose the fractional part.

This code snippet (playground) illustrates @icza's suggestion:

package main

import (
    "fmt"
    "time"
    "strconv"
)

const t = "20170116111621978"

func main() {

    dateStr := t[:len(t)-3]
    millisStr := t[len(t)-3:]
    parsed, err := time.Parse("20060102150405", dateStr)
    if err != nil {
        panic(err)
    }
    millis, _ := strconv.Atoi(millisStr)
    parsed = parsed.Add(time.Duration(millis)*time.Millisecond)
    
    // prints true
    fmt.Println(parsed.UnixNano() == time.Date(2017, 1, 16, 11, 16, 21, 978000000, time.UTC).UnixNano())
}

Icza's answer seems to be significantly more performant than Steven Penny's:

$ go test -bench=. ./parsedate_test.go -benchtime=10s
goos: darwin
goarch: amd64
cpu: Intel(R) Core(TM) i7-7660U CPU @ 2.50GHz
BenchmarkParseMillisSscanf-4         5538606          2159 ns/op         144 B/op         16 allocs/op
BenchmarkParseMillisAddTime-4       74758664           157.4 ns/op         0 B/op          0 allocs/op
PASS
ok      command-line-arguments  26.086s
Related