ZetCode

Go datetime parse

last modified October 30, 2026

In this article we show how to parse date and time strings in Go. Parsing is the reverse of formatting: a layout describes the expected text, and the time package turns the text into a time.Time value.

The two functions used for parsing are:

func Parse(layout, value string) (Time, error)
func ParseInLocation(layout, value string, loc *Location) (Time, error)

Parse interprets the text in UTC unless the text itself contains a time zone. ParseInLocation uses the given location for values without a zone.

The layout uses the reference instant Mon Jan 2 15:04:05 MST 2006. Go does not use format specifiers such as yyyy-mm-dd; the components of the reference value decide which parts of the input are read. A detailed description of the components is in the Formatting Date and Time in Go article.

All examples in this article were compiled and run with Go 1.27.0.

Parsing a date

A date is parsed with a layout that contains the year, the month and the day in the same order as the input. The result has the time set to midnight.

main.go
package main

import (
    "fmt"
    "time"
)

func main() {

    value := "2022/05/12"

    t, err := time.Parse("2006/01/02", value)
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println(t)
    fmt.Printf("%v %v %v\n", t.Year(), t.Month(), t.Day())
}

The layout 2006/01/02 matches the slashes of the input. The parsed value is in UTC because the text has no zone, and the missing time components default to zero.

$ go run main.go
2022-05-12 00:00:00 +0000 UTC
2022 May 12

Parsing a time of day

A layout may contain only the time. The missing date is then filled with the zero date, January 1 of year 0.

main.go
package main

import (
    "fmt"
    "time"
)

func main() {

    value := "14:55:23"

    t, err := time.Parse("15:04:05", value)
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println(t)
    fmt.Println(t.Format(time.Kitchen))
}

The zero date is useful when only the clock time matters, but it is not a valid calendar date. Use the fields of the parsed value rather than the whole value in that case.

$ go run main.go
0000-01-01 14:55:23 +0000 UTC
2:55PM

Parse errors

When the text does not match the layout, Parse returns the zero time and an error. The error names the offending part of the input.

main.go
package main

import (
    "fmt"
    "time"
)

func main() {

    cases := [][2]string{
        {"2022-13-12", "2006-01-02"},
        {"2022-02-30", "2006-01-02"},
        {"2022-05-12 10:00", "2006-01-02"},
        {"12/05/2022", "2006-01-02"},
        {"2022-5-2", "2006-01-02"},
    }

    for _, c := range cases {
        t, err := time.Parse(c[1], c[0])
        fmt.Printf("%q -> %v | %v\n", c[0], t, err)
    }
}

A month outside 1 to 12 and a day that does not exist in the given month are range errors. Extra text after a complete match is reported separately. A different order of the fields, or a single-digit day where the layout requires 02, fails with cannot parse.

The zero value printed for a failed parse is January 1 of year 1, not the zero date used by a successful parse of a time-only value.

$ go run main.go
"2022-13-12" -> 0001-01-01 00:00:00 +0000 UTC | parsing time "2022-13-12": month out of range
"2022-02-30" -> 0001-01-01 00:00:00 +0000 UTC | parsing time "2022-02-30": day out of range
"2022-05-12 10:00" -> 0001-01-01 00:00:00 +0000 UTC | parsing time "2022-05-12 10:00": extra text: " 10:00"
"12/05/2022" -> 0001-01-01 00:00:00 +0000 UTC | parsing time "12/05/2022" as "2006-01-02": cannot parse "12/05/2022" as "2006"
"2022-5-2" -> 0001-01-01 00:00:00 +0000 UTC | parsing time "2022-5-2" as "2006-01-02": cannot parse "5-2" as "01"

Predefined layouts

The constants from the time package can be used as layouts for parsing as well. The next example reads one value per constant.

main.go
package main

import (
    "fmt"
    "time"
)

func main() {

    dates := []string{
        "Sat May 28 11:54:40 CEST 2022",
        "Sat May 28 11:54:40 2022",
        "Sat, 28 May 2022 11:54:40 CEST",
        "Sat, 28 May 2022 11:54:40 +0200",
        "28 May 22 11:54 CEST",
        "28 May 22 11:54 +0200",
        "Saturday, 28-May-22 11:54:40 CEST",
        "2022-05-28T11:54:40+02:00",
        "2022-05-28T11:54:40.809289619+02:00",
        "Sat May 28 11:54:40 +0200 2022",
        "05/28 11:04:40AM '22 +0200",
        "May 28 11:54:40",
    }

    layouts := []string{
        time.UnixDate,
        time.ANSIC,
        time.RFC1123,
        time.RFC1123Z,
        time.RFC822,
        time.RFC822Z,
        time.RFC850,
        time.RFC3339,
        time.RFC3339Nano,
        time.RubyDate,
        time.Layout,
        time.Stamp,
    }

    for i := range dates {
        parsed, err := time.Parse(layouts[i], dates[i])
        if err != nil {
            fmt.Println(err)
            continue
        }
        fmt.Println(parsed)
    }
}

The zone abbreviation in the text is preserved. An unknown abbreviation is recorded with an offset of zero rather than rejected. A value without any zone, such as the ANSIC and Stamp inputs, is interpreted as UTC.

$ go run main.go
2022-05-28 11:54:40 +0200 CEST
2022-05-28 11:54:40 +0000 UTC
2022-05-28 11:54:40 +0200 CEST
2022-05-28 11:54:40 +0200 CEST
2022-05-28 11:54:00 +0200 CEST
2022-05-28 11:54:00 +0200 CEST
2022-05-28 11:54:40 +0200 CEST
2022-05-28 11:54:40 +0200 CEST
2022-05-28 11:54:40.809289619 +0200 CEST
2022-05-28 11:54:40 +0200 CEST
2022-05-28 11:04:40 +0200 CEST
0000-05-28 11:54:40 +0000 UTC

Fractional seconds

A fractional second in the input is accepted even when the layout does not mention it. The opposite is not true: a layout with .000 requires the fraction to be present.

main.go
package main

import (
    "fmt"
    "time"
)

func main() {

    t, err := time.Parse("2006-01-02T15:04:05", "2026-10-30T09:05:07.25")
    fmt.Println(t, err)

    t, err = time.Parse("2006-01-02T15:04:05.000", "2026-10-30T09:05:07")
    fmt.Println(t, err)
}

The first call keeps the 250 milliseconds. The second fails, because the text ends where the layout expects a decimal point and three digits.

$ go run main.go
2026-10-30 09:05:07.25 +0000 UTC <nil>
0001-01-01 00:00:00 +0000 UTC parsing time "2026-10-30T09:05:07" as "2006-01-02T15:04:05.000": cannot parse "" as ".000"

Zone names and offsets

A zone can be written as an abbreviation, as a numeric offset, or as Z. An abbreviation that is not in the local zone database becomes a fabricated zone with a zero offset, which silently changes the instant.

main.go
package main

import (
    "fmt"
    "time"
)

func main() {

    t, err := time.Parse(time.RFC1123, "Fri, 30 Oct 2026 09:05:07 XYZ")
    if err != nil {
        fmt.Println(err)
        return
    }

    name, offset := t.Zone()
    fmt.Println(t)
    fmt.Println(name, offset)
    fmt.Println(t.UTC())

    for _, value := range []string{
        "2026-10-30T09:05:07Z",
        "2026-10-30T09:05:07",
        "2026-10-30T09:05:07+05:30",
    } {
        t, err := time.Parse(time.RFC3339, value)
        fmt.Printf("%-26s %v %v\n", value, t, err)
    }
}

XYZ is unknown, so it is kept as a name with offset 0 and the instant equals UTC. RFC 3339 requires the zone: the second value has no zone and is rejected, while the last one is converted from the +05:30 offset.

$ go run main.go
2026-10-30 09:05:07 +0000 XYZ
XYZ 0
2026-10-30 09:05:07 +0000 UTC
2026-10-30T09:05:07Z      2026-10-30 09:05:07 +0000 UTC <nil>
2026-10-30T09:05:07       0001-01-01 00:00:00 +0000 UTC parsing time "2026-10-30T09:05:07" as "2006-01-02T15:04:05Z07:00": cannot parse "" as "Z07:00"
2026-10-30T09:05:07+05:30 2026-10-30 09:05:07 +0530 +0530 <nil>

ParseInLocation

ParseInLocation assigns the given location to a value that has no zone. The wall clock stays the same, so two locations produce two different instants.

main.go
package main

import (
    "fmt"
    "log"
    "time"
)

func main() {

    date := "Sat May 28 11:54:40 2022"

    utc, err := time.Parse(time.ANSIC, date)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Parse:        ", utc)

    bratislava, err := time.LoadLocation("Europe/Bratislava")
    if err != nil {
        log.Fatal(err)
    }

    local, err := time.ParseInLocation(time.ANSIC, date, bratislava)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Bratislava:   ", local)

    tokyo, err := time.LoadLocation("Asia/Tokyo")
    if err != nil {
        log.Fatal(err)
    }

    japan, err := time.ParseInLocation(time.ANSIC, date, tokyo)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Tokyo:        ", japan)
    fmt.Println("same instant: ", local.Equal(japan))
}

All three values show 11:54:40, but they are three different moments. The locations observe summer time on May 28, so Bratislava is two hours ahead of UTC and Tokyo nine hours ahead.

$ go run main.go
Parse:         2022-05-28 11:54:40 +0000 UTC
Bratislava:    2022-05-28 11:54:40 +0200 CEST
Tokyo:         2022-05-28 11:54:40 +0900 JST
same instant:  false

Month and weekday names

Month and weekday names are matched without regard to case. The form of the name still has to match the layout: Jan reads an abbreviation and January a full name.

main.go
package main

import (
    "fmt"
    "time"
)

func main() {

    t, err := time.Parse("Jan 2 2006", "OCT 30 2026")
    fmt.Println(t, err)

    t, err = time.Parse("Monday, 2 January 2006", "friday, 30 october 2026")
    fmt.Println(t, err)
}

Both values are accepted and normalized. The weekday in the input is not used to compute the date, so a wrong weekday is not detected.

$ go run main.go
2026-10-30 00:00:00 +0000 UTC <nil>
2026-10-30 00:00:00 +0000 UTC <nil>

Trying several layouts

Real input often comes in more than one format. A common approach is to try a list of layouts and stop at the first one that parses.

main.go
package main

import (
    "fmt"
    "time"
)

func main() {

    input := "31/12/2026"

    layouts := []struct {
        name   string
        layout string
    }{
        {"ISO", "2006-01-02"},
        {"European", "02.01.2006"},
        {"long", "January 2, 2006"},
        {"short", "02/01/2006"},
    }

    for _, l := range layouts {
        t, err := time.Parse(l.layout, input)
        if err != nil {
            continue
        }
        fmt.Printf("matched %s: %s\n", l.name, t.Format(time.DateOnly))
        return
    }

    fmt.Println("no layout matched")
}

Only the last layout matches. The order of the list matters when two layouts can read the same text, because the first success wins.

$ go run main.go
matched short: 2026-12-31

Unix timestamps

A Unix timestamp is not parsed with a layout. The number is converted with strconv.ParseInt and then passed to time.Unix, which returns a local time; UTC makes the result independent of the machine's zone.

main.go
package main

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

func main() {

    sec, err := strconv.ParseInt("1793386446", 10, 64)
    if err != nil {
        fmt.Println(err)
        return
    }

    t := time.Unix(sec, 0).UTC()
    fmt.Println(t.Format(time.RFC3339))

    milli := time.UnixMilli(1793386446954).UTC()
    fmt.Println(milli.Format("2006-01-02 15:04:05.000"))
}

time.Unix takes seconds and nanoseconds, while time.UnixMilli and time.UnixMicro take the smaller units directly.

$ go run main.go
2026-10-30T18:54:06Z
2026-10-30 18:54:06.954

Parsing durations

A duration is a different type from a time. time.ParseDuration accepts a signed sequence of decimal numbers with units such as h, m, s, ms and us.

main.go
package main

import (
    "fmt"
    "time"
)

func main() {

    d, err := time.ParseDuration("2h45m30s")
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println(d)
    fmt.Println(d.Minutes())

    _, err = time.ParseDuration("5 hours")
    fmt.Println(err)
}

The units must be written directly after the number and without spaces. The error for the second call names the unknown unit.

$ go run main.go
2h45m30s
165.5
time: unknown unit " hours" in duration "5 hours"

Parsing and formatting

The usual workflow reads text with a layout and then writes it with a different layout or in a different location.

main.go
package main

import (
    "fmt"
    "log"
    "time"
)

func main() {

    t, err := time.Parse("02 Jan 2006 15:04 MST", "30 Oct 2026 09:05 UTC")
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(t.Format(time.RFC3339))

    tokyo, err := time.LoadLocation("Asia/Tokyo")
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(t.In(tokyo).Format("Mon 2 Jan 2006, 15:04 MST"))
}

The same instant is rendered in UTC and in Tokyo. Only the presentation changes; the value itself is not modified.

$ go run main.go
2026-10-30T09:05:00Z
Fri 30 Oct 2026, 18:05 JST

Source

Go time package - reference

In this article we have parsed date and time values in Go. We covered time.Parse and time.ParseInLocation, the error messages for invalid input, predefined layouts, fractional seconds, zone abbreviations and offsets, case-insensitive names, a multi-layout fallback, Unix timestamps, durations and the round trip from text to a formatted value.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Go tutorials.