78

How do I parse non-standard date/time strings in Go. In example if I wanted to convert the string 10/15/1983 into a time.Time? The time.Parse() function supposedly allows you to specify a format.

http://play.golang.org/p/v5DbowXt1x

package main

import "fmt"
import "time"

func main() {
    test, err := time.Parse("10/15/1983", "10/15/1983")
    if err != nil {
        panic(err)
    }

    fmt.Println(test)
}

This results in a panic.

panic: parsing time "10/15/1983" as "10/15/1983": cannot parse "" as "0/"

Logically that makes sense because how is it supposed to know which is the day and which is the month.

Other languages have a function similar to the following:

parse("mm/dd/yyyy", "10/15/1983")

I cannot find such a function in the Go docs, is my only choice to regex?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Owen Allen
  • 11,348
  • 9
  • 51
  • 63
  • 4
    You might want to look at [this blog](http://pauladamsmith.com/blog/2011/05/go_time.html) by Paul Smith – Mark Hall Dec 31 '12 at 22:43

4 Answers4

178

There are some key values that the time.Parse is looking for.

By changing:

test, err := time.Parse("10/15/1983", "10/15/1983")

to

test, err := time.Parse("01/02/2006", "10/15/1983")

the parser will recognize it.

Here's the modified code on the playground.

package main

import "fmt"
import "time"

func main() {
    test, err := time.Parse("01/02/2006", "10/15/1983")
    if err != nil {
        panic(err)
    }

    fmt.Println(test)
}


You can utilize the constants list in the src/pkg/time/format.go file to create your own parse formats.

const (
    stdLongMonth      = "January"
    stdMonth          = "Jan"
    stdNumMonth       = "1"
    stdZeroMonth      = "01"
    stdLongWeekDay    = "Monday"
    stdWeekDay        = "Mon"
    stdDay            = "2"
    stdUnderDay       = "_2"
    stdZeroDay        = "02"
    stdHour           = "15"
    stdHour12         = "3"
    stdZeroHour12     = "03"
    stdMinute         = "4"
    stdZeroMinute     = "04"
    stdSecond         = "5"
    stdZeroSecond     = "05"
    stdLongYear       = "2006"
    stdYear           = "06"
    stdPM             = "PM"
    stdpm             = "pm"
    stdTZ             = "MST"
    stdISO8601TZ      = "Z0700"  // prints Z for UTC
    stdISO8601ColonTZ = "Z07:00" // prints Z for UTC
    stdNumTZ          = "-0700"  // always numeric
    stdNumShortTZ     = "-07"    // always numeric
    stdNumColonTZ     = "-07:00" // always numeric
)

So anytime your format specifies a year, it should be done with "06" or "2006", seconds are specified by "05" or "5" and time zones are specified at "MST", "Z0700", "Z07:00", "-0700", "-07" or "-07:00". If you reference the constants list you can likely put together any standard format you'd need to parse.

For example, if you want to parse the date/time in the Common Log Format, the format Apache uses for its log files, you would do so by passing the following string to time.Parse() as the layout argument.

"02/Jan/2006:15:04:05 -0700"

"02" denotes the day of the month field, "Jan" denotes the month name field, "2006" denotes the year field, "15" denotes the hour of day field in 24 hour format, "04" denotes the minutes field, "05" denotes the seconds field and "-0700" denotes the time zone field.

That format would parse the current PST time: 31/Dec/2012:15:32:25 -0800

So the time.Parse() call would look like this:

test, err := time.Parse("02/Jan/2006:15:04:05 -0700", "31/Dec/2012:15:32:25 -0800")
Daniel
  • 38,041
  • 11
  • 92
  • 73
  • 2
    Your code works, but why? If I change it to `time.Parse("01/02/2005", "10/15/1983")` it fails... what is magic about 2006 as opposed to 2005, 2004, 1990 etc? – Owen Allen Dec 31 '12 at 22:47
  • 9
    Upon re-reading the docs I think this point could be made a bit more clearly in them. I couple real world examples would make it much clearer. In my futile attempts I tried things like `time.Parse("stdLongMonth/stdLongDay/stdLongYear", "10/15/83")` =p. – Owen Allen Dec 31 '12 at 23:00
  • 76
    Wow, the Go docs could have mentioned that the numbers you use in your date format actually matter. – Matt Nov 04 '13 at 21:42
  • Hi Nuncleon. You made 2 mistakes there. First of all you selected wrong constants for your format: should be stdZeroMonth/stdZeroDay/stdYear and second thing that you need to use the values of this constants. So for your case you need to parse like this: `time.Parse("01/02/06", "10/15/83")` – Bogdan Nechyporenko Sep 03 '14 at 21:10
  • 2
    What a sneaky approach! Thank you for explaining it! +1 – Alix Axel Dec 23 '14 at 04:16
  • It's not possible to format a 24 hour format *without leading zeroes*? – Alix Axel Dec 23 '14 at 18:09
  • thanks for the explanation really interesting implementation. – Jeremy Quinton Jan 09 '17 at 10:35
  • This a wonderful answer and cleared all my doubts about the time package – Shubhang b Jul 05 '23 at 16:26
8

If you can't remember the Numbers in the specifying layout ("2006-01-02T15:04:05.000Z"), you may use my simple date formatting library github.com/metakeule/fmtdate that uses MS Excel conventions, like Y,M,D,h and internally translates them to the number format:

package main

import (
    "github.com/metakeule/fmtdate"

    "fmt"
)

func main() {
    test, err := fmtdate.Parse("MM/DD/YYYY", "10/15/1983")
    if err != nil {
        panic(err)
    }

    fmt.Println(test)
}
metakeule
  • 3,724
  • 2
  • 23
  • 29
  • 1
    For people that can't remember, 1,2,3,4,5,6,7 (01/02 03:04:05PM '06 -07)?? At least something like a strftime format string has support for more things, your spreadsheet format doesn't even have 12 hour am/pm support so is a step backwards in several ways. – Dave C May 02 '15 at 14:16
  • 3
    @DaveC Well we Germans don't think in am/pm, so 15 to 3 is a mindshift, also you must remember the order: Day, Month, Hour/PM, Minute, Second, Year, Timezone. The position of the year is weird. However I always had to look it up and I am not the only one.... Also for someone not familiar with the go time format, the excel format is obviously a format and can't be confused with a real date. – metakeule May 02 '15 at 20:09
  • 1
    @DaveC I have updated the library to support time zones and AM/PM. Anything missing? – metakeule May 02 '15 at 21:18
2

If you are looking for a C-Style formatting function: After reviewing some of the options I have chosen https://github.com/cactus/gostrftime as it generally follows the strfmt(3) notation.

To quote the example:

import (
    "fmt"
    "time"
    "github.com/cactus/gostrftime"
)

func main() {
    now := time.Now()
    fmt.Println(gostrftime.Format("%Y-%m-%d", now))
}

If a date format has to be used by both C and Go and the C implementation is not to be touched there's no choice but to adapt on the Go end. The above package fulfills that need.

Justus Wingert
  • 472
  • 4
  • 9
0

If you don't want bother remembering the magic numbers, you can do this:

package main

import (
   "fmt"
   "time"
)

func main() {
   var (
      y, d int
      m time.Month
   )
   fmt.Sscanf("10/15/1983", "%v/%v/%v", &m, &d, &y)
   t := time.Date(y, m, d, 0, 0, 0, 0, time.UTC)
   fmt.Println(t) // 1983-10-15 00:00:00 +0000 UTC
}

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

Nimantha
  • 6,405
  • 6
  • 28
  • 69
Zombo
  • 1
  • 62
  • 391
  • 407