Go datetime format
last modified October 30, 2026
In this article we show how to format date and time values in Go. Formatting
turns a time.Time value into text with the
Format method, which takes a layout string.
Go does not use format specifiers such as yyyy-MM-dd. Instead, a
layout is a sample date, and the reference instant is
Mon Jan 2 15:04:05 MST 2006
Every component of that sample has a fixed value, and the same component in a
layout is replaced by the corresponding part of the formatted value. The year
is 2006, the month is 01 or January, the
day is 02 or 2, and so on.
The signature of the method is:
func (t Time) Format(layout string) string
All examples in this article were compiled and run with Go 1.27.0. The
examples that use time.Now print different values on every run.
The reference layout
The reference instant is Monday, January 2, 15:04:05 (that is, 3:04:05 PM) in the year 2006, in the MST time zone. The components of this value are the only ones the formatter recognizes.
package main
import (
"fmt"
"time"
)
func main() {
t := time.Date(2026, time.October, 30, 9, 5, 7, 0, time.UTC)
fmt.Println(t.Format("2006-01-02"))
fmt.Println(t.Format("02 Jan 2006"))
fmt.Println(t.Format("15:04:05"))
fmt.Println(t.Format("3:04 PM"))
}
The first layout produces an ISO-like date, the second a human readable date,
and the last two a 24-hour and a 12-hour clock. The value t is
built with time.Date so that the output does not depend on the
current clock.
$ go run main.go 2026-10-30 30 Oct 2026 09:05:07 9:05 AM
Formatting the current time
A typical program formats time.Now instead of a fixed value.
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Println("Time: ", now.Format("15:04:05"))
fmt.Println("Short date:", now.Format("Jan 2, 2006"))
fmt.Println("Long date: ", now.Format("Mon Jan 2 15:04:05 2006"))
fmt.Println("ISO 8601: ", now.Format(time.RFC3339))
}
The output depends on the moment of execution. The layout
Jan 2, 2006 is not padded, so a single-digit day appears without a
leading zero.
$ go run main.go Time: 19:57:59 Short date: Oct 30, 2026 Long date: Fri Oct 30 19:57:59 2026 ISO 8601: 2026-10-30T19:57:59+01:00
Predefined layouts
The time package defines a set of layout constants for the common
internet date formats and for its own timestamp formats.
package main
import (
"fmt"
"time"
)
func main() {
t := time.Date(2026, time.October, 30, 9, 5, 7, 123456789,
time.FixedZone("CET", 3600))
fmt.Println(t.Format(time.Layout))
fmt.Println(t.Format(time.ANSIC))
fmt.Println(t.Format(time.UnixDate))
fmt.Println(t.Format(time.RubyDate))
fmt.Println(t.Format(time.RFC822))
fmt.Println(t.Format(time.RFC822Z))
fmt.Println(t.Format(time.RFC850))
fmt.Println(t.Format(time.RFC1123))
fmt.Println(t.Format(time.RFC1123Z))
fmt.Println(t.Format(time.RFC3339))
fmt.Println(t.Format(time.RFC3339Nano))
fmt.Println(t.Format(time.Kitchen))
fmt.Println(t.Format(time.Stamp))
fmt.Println(t.Format(time.StampMilli))
fmt.Println(t.Format(time.StampMicro))
fmt.Println(t.Format(time.StampNano))
fmt.Println(t.Format(time.DateTime))
fmt.Println(t.Format(time.DateOnly))
fmt.Println(t.Format(time.TimeOnly))
}
The value uses a fixed zone named CET with an offset of one hour, so the zone
abbreviation, the numeric offset and the milliseconds are visible in the
output. The Z variants of the RFC layouts print +0100
instead of the zone name, while RFC 3339 uses a colon in the offset.
$ go run main.go 10/30 09:05:07AM '26 +0100 Fri Oct 30 09:05:07 2026 Fri Oct 30 09:05:07 CET 2026 Fri Oct 30 09:05:07 +0100 2026 30 Oct 26 09:05 CET 30 Oct 26 09:05 +0100 Friday, 30-Oct-26 09:05:07 CET Fri, 30 Oct 2026 09:05:07 CET Fri, 30 Oct 2026 09:05:07 +0100 2026-10-30T09:05:07+01:00 2026-10-30T09:05:07.123456789+01:00 9:05AM Oct 30 09:05:07 Oct 30 09:05:07.123 Oct 30 09:05:07.123456 Oct 30 09:05:07.123456789 2026-10-30 09:05:07 2026-10-30 09:05:07
Date components
The year has two forms: 2006 prints all four digits and
06 the last two. The month can be written as a number
(01, 1) or as a name (January,
Jan).
package main
import (
"fmt"
"time"
)
func main() {
t := time.Date(2026, time.October, 5, 9, 5, 7, 0, time.UTC)
fmt.Println(t.Format("2006"))
fmt.Println(t.Format("06"))
fmt.Println(t.Format("January"))
fmt.Println(t.Format("Jan"))
fmt.Println(t.Format("01"))
fmt.Println(t.Format("1"))
}
October is the tenth month, so 01 and 1 both print
10 here. The difference appears only for months with a single
digit.
$ go run main.go 2026 26 October Oct 10 10
Day components
The day of the month is 02 for the zero-padded form, 2
for the plain form and _2 for the space-padded form. The weekday
is Monday or Mon, and the day of the year is
002.
package main
import (
"fmt"
"time"
)
func main() {
t := time.Date(2026, time.October, 5, 9, 5, 7, 0, time.UTC)
fmt.Println(t.Format("02"))
fmt.Println(t.Format("2"))
fmt.Println(t.Format("_2"))
fmt.Println(t.Format("Monday"))
fmt.Println(t.Format("Mon"))
fmt.Println(t.Format("002"))
}
October 5, 2026 is a Monday and the 278th day of the year. In a non-leap year the last three months start at days 274, 305 and 335, so the count is easy to check.
$ go run main.go 05 5 5 Monday Mon 278
Padding
The difference between the padded and the plain forms is visible only when the
value is smaller than ten. The _2 form pads with a space, which
keeps the width of a column constant.
package main
import (
"fmt"
"time"
)
func main() {
june := time.Date(2026, time.June, 5, 9, 5, 7, 0, time.UTC)
jan := time.Date(2026, time.January, 15, 9, 5, 7, 0, time.UTC)
fmt.Printf("%q %q %q\n", june.Format("1"), june.Format("01"), june.Format("Jan"))
fmt.Printf("%q %q %q\n", june.Format("2"), june.Format("02"), june.Format("_2"))
fmt.Printf("%q %q %q\n", jan.Format("2"), jan.Format("02"), jan.Format("_2"))
}
The %q verb quotes the strings, so the leading space of
_2 is visible. The first line is the month, the next two are the
day: June 5 gives " 5", while January 15 already has two digits
and gives "15".
$ go run main.go "6" "06" "Jun" "5" "05" " 5" "15" "15" "15"
Time components
The hour is 15 in the 24-hour clock and 3 or
03 in the 12-hour clock. The minute is 04 or
4, the second is 05 or 5, and the
meridiem is PM or pm.
package main
import (
"fmt"
"time"
)
func main() {
t := time.Date(2026, time.October, 5, 9, 5, 7, 0, time.UTC)
fmt.Println(t.Format("15:04:05"))
fmt.Println(t.Format("3:04:05 PM"))
fmt.Println(t.Format("03:04:05 pm"))
fmt.Println(t.Format("15"), t.Format("3"))
fmt.Println(t.Format("04"), t.Format("4"))
fmt.Println(t.Format("05"), t.Format("5"))
}
Note that 3 is an hour, not a minute; minutes and seconds are
4 and 5. The same value appears as
09:05:07 in the 24-hour clock and 9:05:07 AM in the
12-hour clock: 03 adds a leading zero, and the meridiem token adds
AM or PM.
$ go run main.go 09:05:07 9:05:07 AM 09:05:07 am 09 9 05 5 07 7
Fractional seconds
A trailing .000 prints exactly the given number of digits and
truncates the rest. A trailing .999 removes trailing zeros, so the
number of printed digits varies. Both forms follow the decimal point and can be
used after the seconds.
package main
import (
"fmt"
"time"
)
func main() {
a := time.Date(2026, time.January, 2, 3, 4, 5, 120000000, time.UTC)
b := time.Date(2026, time.January, 2, 3, 4, 5, 0, time.UTC)
fmt.Println(a.Format("15:04:05.000"))
fmt.Println(a.Format("15:04:05.999"))
fmt.Println(b.Format("15:04:05.000"))
fmt.Println(b.Format("15:04:05.999"))
fmt.Println(a.Format(time.RFC3339Nano))
fmt.Println(b.Format(time.RFC3339Nano))
}
The value a has 120 milliseconds, so .000 prints
.120 and .999 prints .12. The value
b has no fraction: .000 still prints
.000, while .999 prints nothing, and
RFC3339Nano ends directly with Z.
$ go run main.go 03:04:05.120 03:04:05.12 03:04:05.000 03:04:05 2026-01-02T03:04:05.12Z 2026-01-02T03:04:05Z
Time zones
MST prints the zone abbreviation, -0700 and
-07:00 the numeric offset, and the Z forms print
Z for the UTC zone instead of +0000.
package main
import (
"fmt"
"time"
)
func main() {
utc := time.Date(2026, time.October, 30, 9, 5, 7, 0, time.UTC)
cet := utc.In(time.FixedZone("CET", 3600))
fmt.Println(utc.Format("MST -0700 -07:00 Z07:00 Z0700"))
fmt.Println(cet.Format("MST -0700 -07:00 Z07:00 Z0700"))
}
Both values describe the same instant. The UTC value prints UTC
and Z; the CET value prints the name and +0100.
$ go run main.go UTC +0000 +00:00 Z Z CET +0100 +01:00 +01:00 +0100
Week numbers and day of year
The layout token 002 is the zero-padded day of the year, while
ISOWeek returns the ISO 8601 year and week numbers.
package main
import (
"fmt"
"time"
)
func main() {
t := time.Date(2026, time.June, 5, 9, 5, 7, 0, time.UTC)
fmt.Println(t.Format("2006-002"))
year, week := t.ISOWeek()
fmt.Println(year, week)
}
June 5, 2026 is day 156 of the year and falls in ISO week 23. The ISO year can
differ from the calendar year near New Year, which is why
ISOWeek returns both values.
$ go run main.go 2026-156 2026 23
Combining layouts
Layouts are ordinary strings, so they can contain any text, including words and punctuation. Only the recognized components are replaced.
package main
import (
"fmt"
"time"
)
func main() {
t := time.Date(2026, time.October, 5, 9, 5, 7, 0, time.UTC)
fmt.Println(t.Format("Monday, 2 January 2006"))
fmt.Println(t.Format("02 Jan 2006 15:04 MST"))
fmt.Println(t.Format("2006/01/02"))
fmt.Println(t.Format("01/02/06"))
fmt.Println(t.Format(time.DateOnly + " " + time.TimeOnly))
fmt.Println(t.Format("15:04"))
fmt.Println(t.Format("3:04 PM"))
fmt.Println(t.Format("It is 15:04 on Monday"))
}
The layout constants can be concatenated because they are strings. The last line shows that literal words are copied unchanged.
$ go run main.go Monday, 5 October 2026 05 Oct 2026 09:05 UTC 2026/10/05 10/05/26 2026-10-05 09:05:07 09:05 9:05 AM It is 09:05 on Monday
Wrong layouts
Only the components of the reference time are replaced. Letters that do not form a component are copied literally, and stray digits can be read as a different component.
package main
import (
"fmt"
"time"
)
func main() {
t := time.Date(2026, time.October, 30, 9, 5, 7, 0, time.UTC)
fmt.Println(t.Format("YYYY-MM-DD"))
fmt.Println(t.Format("2026-10-30"))
fmt.Println(t.Format("2026-13-02"))
}
The first layout prints its own letters, because YYYY,
MM and DD are not Go components. The second line
looks like a date but is a literal, so it always prints the same text. In the
third line 13 is read as the month token 1 followed by
the hour token 3, which produces the meaningless
2026-109-30.
$ go run main.go YYYY-MM-DD 2026-10-30 2026-109-30
Building strings with AppendFormat
AppendFormat writes the formatted value to an existing byte slice
and returns the extended slice. It avoids an intermediate string allocation and
is convenient when several values are joined.
package main
import (
"fmt"
"time"
)
func main() {
t := time.Date(2026, time.October, 30, 9, 5, 7, 0, time.UTC)
b := []byte("date: ")
b = t.AppendFormat(b, time.DateOnly)
b = append(b, ", time: "...)
b = t.AppendFormat(b, time.TimeOnly)
fmt.Println(string(b))
}
The buffer starts with the prefix date: , receives the date, the
literal separator and the time. The result is the same as concatenating three
Format calls.
$ go run main.go date: 2026-10-30, time: 09:05:07
Default string forms
Printing a time.Time without a layout uses String,
which combines the date, the time, the offset and the zone name.
GoString produces the source expression that would recreate the
value.
package main
import (
"fmt"
"time"
)
func main() {
t := time.Date(2026, time.October, 30, 9, 5, 7, 0, time.UTC)
fmt.Println(t.String())
fmt.Println(t.GoString())
}
String is the form used by fmt.Println.
GoString is what the %#v verb prints, and it is handy
when a time has to be pasted back into source code.
$ go run main.go 2026-10-30 09:05:07 +0000 UTC time.Date(2026, time.October, 30, 9, 5, 7, 0, time.UTC)
Source
In this article we have formatted date and time values in Go. We covered the
reference layout, the predefined layout constants, the individual date, day and
time components, padding, fractional seconds, time zones, week numbers,
combined and incorrect layouts, and the AppendFormat,
String and GoString methods.
Author
List all Go tutorials.