Question
I am trying to convert a Unix timestamp into a time.Time value in Go, but I get an "out of range" error. This is confusing because I thought the layout I used was valid according to the Go documentation.
Here is the code:
package main
import (
"fmt"
"time"
)
func main() {
tm, err := time.Parse("1136239445", "1405544146")
if err != nil {
panic(err)
}
fmt.Println(tm)
}
Why does this fail, and what is the correct way to convert a Unix timestamp to time.Time in Go?
Short Answer
By the end of this page, you will understand why time.Parse() does not work for Unix timestamps in Go, how Go's time layout system actually works, and how to correctly convert Unix seconds into a time.Time using time.Unix(). You will also learn common mistakes, practical usage patterns, and how this appears in real Go codebases.
Concept
In Go, time.Parse() is used for parsing formatted date/time strings, not raw Unix timestamps.
A Unix timestamp is typically a number representing the number of seconds since:
1970-01-01 00:00:00 UTC
For example:
1405544146means a specific moment in time measured as elapsed seconds since the Unix epoch.
Why time.Parse() fails
The first argument to time.Parse() is not a pattern like YYYY-MM-DD and it is not an example of your input format in a generic sense. In Go, the layout must use the special reference date:
Mon Jan 2 15:04:05 MST 2006
or pieces of it, such as:
2006-01-02
15:04:05
When you write:
time.Parse("1136239445", "1405544146")
Go tries to interpret 1136239445 as a time layout based on the reference date rules. Since it is just a random number and not a valid layout, parsing fails.
The correct tool: time.Unix()
To convert a Unix timestamp to time.Time, use:
time.Unix(seconds, nanoseconds)
Example:
tm := time.Unix(1405544146, 0)
This creates a time.Time from Unix seconds directly.
Why this matters
In real programs, timestamps often come from:
- APIs
- databases
- log files
- message queues
- JSON payloads
Knowing when to use time.Parse() versus time.Unix() prevents bugs and makes your code clearer.
Mental Model
Think of Go time handling like having two different doors:
time.Parse()is the door for reading a clock/calendar string such as"2024-06-10 14:30:00"time.Unix()is the door for reading a stopwatch counter such as1405544146
A formatted date string says:
- "Here is the date written in human-readable form."
A Unix timestamp says:
- "Here is the number of seconds since a fixed starting point."
If you try to send a stopwatch number through the calendar-string door, it will not fit. That is exactly what happened in the original code.
Syntax and Examples
Core syntax
Parsing a formatted date string
parsedTime, err := time.Parse("2006-01-02 15:04:05", "2024-06-10 12:30:00")
Use this when your input is a human-readable date/time string.
Converting a Unix timestamp
t := time.Unix(1405544146, 0)
Use this when your input is seconds since the Unix epoch.
Beginner-friendly example
package main
import (
"fmt"
"time"
)
func main() {
timestamp := int64(1405544146)
tm := time.Unix(timestamp, 0)
fmt.Println(tm)
fmt.Println(tm.UTC())
}
Explanation
timestampis stored asint64, which matches whattime.Unix()expects.time.Unix(timestamp, 0)means:- first argument = seconds
- second argument = nanoseconds
fmt.Println(tm)prints the time in the local time zone representation.tm.UTC()shows the same instant in UTC.
If the timestamp is a string
Often timestamps arrive as strings from JSON, forms, or APIs.
package main
import (
"fmt"
"strconv"
"time"
)
func main() {
raw := "1405544146"
seconds, err := strconv.ParseInt(raw, 10, 64)
if err != nil {
panic(err)
}
tm := time.Unix(seconds, 0)
fmt.Println(tm)
}
Here:
strconv.ParseInt()converts the string to an integer.time.Unix()converts that integer totime.Time.
Step by Step Execution
Consider this example:
package main
import (
"fmt"
"time"
)
func main() {
ts := int64(1405544146)
tm := time.Unix(ts, 0)
fmt.Println(tm.UTC())
}
Step by step
1. Create a timestamp value
ts := int64(1405544146)
tsstores the Unix timestamp.- It is an
int64, which is the expected type fortime.Unix().
2. Convert it to time.Time
tm := time.Unix(ts, 0)
tsis treated as seconds since1970-01-01 00:00:00 UTC.0means there are no extra nanoseconds.- Go creates a
time.Timevalue representing that exact instant.
3. Print the result in UTC
fmt.Println(tm.UTC())
tm.UTC()converts the display to UTC.- This helps avoid confusion caused by local time zones.
What if you used time.Parse() instead?
tm, err := time.Parse("1136239445", "1405544146")
Go would try to treat 1136239445 as a layout string based on the special reference date format. Since it is not a valid layout, parsing fails.
So the important rule is:
- Use
time.Parse()for formatted date strings - Use
time.Unix()for Unix timestamps
Real World Use Cases
Unix timestamps are extremely common in real software.
APIs
Many APIs send timestamps like:
{"created_at": 1718012000}
In Go, you convert them with time.Unix() before displaying or comparing dates.
Logging systems
Log entries may store event times as Unix seconds or milliseconds. Converting them to time.Time makes them easier to format and analyze.
Databases
Some databases or legacy systems store timestamps as integers instead of full datetime values. Go applications often convert those integers into time.Time when reading records.
Expiration and scheduling
Applications frequently check whether a timestamp is before or after the current time:
expiresAt := time.Unix(expirySeconds, 0)
if time.Now().After(expiresAt) {
fmt.Println("expired")
}
Data processing scripts
CSV, JSON, or event-stream data often contains epoch-based timestamps. Converting them lets you group, sort, and filter records by time.
Real Codebase Usage
In real Go codebases, developers usually do more than a simple conversion.
Parse, validate, convert
If a timestamp comes in as a string, a common pattern is:
func parseUnixTimestamp(raw string) (time.Time, error) {
seconds, err := strconv.ParseInt(raw, 10, 64)
if err != nil {
return time.Time{}, fmt.Errorf("invalid unix timestamp: %w", err)
}
return time.Unix(seconds, 0), nil
}
This pattern keeps parsing logic reusable.
Guard clauses
Developers often reject invalid input early:
func process(raw string) error {
if raw == "" {
return fmt.Errorf("timestamp is required")
}
seconds, err := strconv.ParseInt(raw, 10, 64)
if err != nil {
return fmt.Errorf("bad timestamp: %w", err)
}
tm := time.Unix(seconds, 0)
fmt.Println(tm)
return nil
}
Handling milliseconds
Some systems send milliseconds, not seconds. A common codebase pattern is dividing by 1000 or using time.UnixMilli() when available.
ms := int64(1405544146000)
t := time.UnixMilli(ms)
If using older code or different style:
ms := int64(1405544146000)
t := time.Unix(0, ms*int64(time.Millisecond))
Time zone normalization
Teams often normalize to UTC before storing or comparing times:
t := time.Unix(seconds, 0).UTC()
This reduces confusion in distributed systems.
JSON and API layers
A service may receive a timestamp string, convert it, validate it, and then store a time.Time in the domain model. This keeps the rest of the code working with proper time values instead of raw strings.
Common Mistakes
1. Using time.Parse() for Unix timestamps
Broken code:
tm, err := time.Parse("1136239445", "1405544146")
Why it is wrong:
time.Parse()expects a layout based on Go's reference date.- A Unix timestamp is not a formatted date string.
Fix:
tm := time.Unix(1405544146, 0)
2. Forgetting to convert a string to an integer first
Broken code:
raw := "1405544146"
tm := time.Unix(raw, 0)
Why it is wrong:
time.Unix()expects integer values, not strings.
Fix:
seconds, err := strconv.ParseInt(raw, 10, 64)
if err != nil {
panic(err)
}
tm := time.Unix(seconds, 0)
3. Confusing seconds with milliseconds
Broken code:
ts := int64(1405544146000)
tm := time.Unix(ts, 0)
Why it is wrong:
- That value looks like milliseconds, but
time.Unix()interprets it as seconds. - The result will be far in the future.
Fix:
ts := int64(1405544146000)
tm := time.UnixMilli(ts)
Or:
tm := time.Unix(0, ts*int64(time.Millisecond))
4. Being confused by time zones
Broken expectation:
fmt.Println(time.Unix(1405544146, 0))
Why it may seem wrong:
- The printed time may use your local time zone.
- The timestamp itself is still correct.
Fix:
fmt.Println(time.Unix(1405544146, 0).UTC())
5. Ignoring parse errors from string input
Broken code:
seconds, _ := strconv.ParseInt("not-a-number", 10, 64)
tm := time.Unix(seconds, 0)
Why it is risky:
- Ignoring the error can silently produce bad results.
Fix:
seconds, err := strconv.ParseInt("not-a-number", 10, 64)
if err != nil {
return
}
tm := time.Unix(seconds, 0)
Comparisons
| Task | Correct Go Tool | Example |
|---|---|---|
| Parse a formatted date string | time.Parse() | time.Parse("2006-01-02", "2024-06-10") |
Convert Unix seconds to time.Time | time.Unix() | time.Unix(1405544146, 0) |
Convert Unix milliseconds to time.Time | time.UnixMilli() | time.UnixMilli(1405544146000) |
Convert Unix nanoseconds to time.Time | time.Unix(0, ns) or time.UnixNano patterns | time.Unix(0, 1405544146000000000) |
time.Parse() vs time.Unix()
| Feature | time.Parse() | time.Unix() |
|---|---|---|
| Input type | Formatted string | Numeric epoch value |
| Example input | "2024-06-10 15:00:00" | 1405544146 |
| Needs layout? | Yes | No |
| Best for | Human-readable dates | Unix timestamps |
Seconds vs milliseconds
| Timestamp type | Example | Correct conversion |
|---|---|---|
| Seconds | 1405544146 | time.Unix(ts, 0) |
| Milliseconds | 1405544146000 | time.UnixMilli(ts) |
| Nanoseconds | 1405544146000000000 | time.Unix(0, ts) |
Cheat Sheet
// Formatted date string -> time.Time
t, err := time.Parse("2006-01-02 15:04:05", "2024-06-10 12:30:00")
// Unix seconds -> time.Time
t := time.Unix(1405544146, 0)
// Unix milliseconds -> time.Time
t := time.UnixMilli(1405544146000)
// String timestamp -> int64 -> time.Time
s := "1405544146"
seconds, err := strconv.ParseInt(s, 10, 64)
t := time.Unix(seconds, 0)
// Show in UTC
fmt.Println(t.UTC())
Rules to remember
time.Parse()is for formatted date strings.time.Unix()is for Unix timestamps in seconds.- Use
strconv.ParseInt()if the timestamp starts as a string. - Watch out for milliseconds vs seconds.
- Use
.UTC()when you want consistent output.
Common layouts in Go
"2006-01-02"
"15:04:05"
"2006-01-02 15:04:05"
These are based on Go's reference time, not custom placeholders like YYYY or MM.
FAQ
Why does time.Parse() not work with Unix timestamps in Go?
time.Parse() expects a formatted date string and a Go layout based on the reference date. A Unix timestamp is just a number of seconds since the epoch, so time.Unix() is the correct function.
How do I convert a Unix timestamp string to time.Time in Go?
First convert the string to int64 with strconv.ParseInt(), then pass it to time.Unix().
seconds, err := strconv.ParseInt("1405544146", 10, 64)
t := time.Unix(seconds, 0)
What is the difference between time.Unix() and time.UnixMilli()?
time.Unix()expects seconds.time.UnixMilli()expects milliseconds.
Use the one that matches your input data.
Why does the printed time look different from what I expected?
Go may print the time in your local time zone. Try using .UTC() to see the timestamp in UTC.
What type should I use for Unix timestamps in Go?
int64 is the usual choice because time.Unix() expects int64 values.
Can I parse "2024-06-10" with time.Unix()?
No. That is a formatted date string, so use time.Parse() instead.
How do I know whether a timestamp is in seconds or milliseconds?
Milliseconds usually have more digits. For modern dates:
- seconds: around 10 digits
- milliseconds: around 13 digits
Always check the API or data source documentation.
Mini Project
Description
Build a small Go program that reads Unix timestamps from strings, converts them into time.Time, and prints both local time and UTC time. This demonstrates the full workflow you will often use in APIs, scripts, or data import tools.
Goal
Create a program that safely converts string-based Unix timestamps into readable Go time.Time values.
Requirements
- Accept at least three timestamp strings in a slice.
- Convert each string to an integer using proper error handling.
- Convert valid timestamps to
time.Timeusingtime.Unix(). - Print both the local time and UTC version for each valid timestamp.
- Skip invalid values without crashing the whole program.
Keep learning
Related questions
Are Go Maps Passed by Value or Reference?
Learn Go map parameter and return semantics: maps are passed by value, while copies share underlying data. Know when map pointers are useful.
Authentication for Go REST APIs: Sessions, Tokens, and Middleware
Learn practical authentication patterns for Go REST APIs, including password hashing, server sessions, bearer tokens, middleware, and security rules.
Automatic Build Versioning in Go: Embed Incrementing Build Numbers
Learn how to add automatic build versioning in Go using linker flags, build metadata, CI counters, and Git-based version values.