Question
In Go, if a function returns a time.Time, I cannot return nil during an error case because Go reports:
cannot use nil as type time.Time in return argument
What is the zero value of time.Time, and how should it be used when a valid time is not available?
Short Answer
By the end of this page, you will understand why time.Time cannot be nil, what its zero value is, how to return it from functions, and how to check whether a time.Time has been set in real Go programs.
Concept
In Go, every type has a zero value. This is the default value a variable gets when you declare it without explicitly assigning anything.
For example:
int→0string→""bool→false- pointers, slices, maps, functions, interfaces →
nil
But time.Time is not a pointer. It is a struct type from Go's time package. Structs cannot be nil unless you use a pointer to them, such as *time.Time.
So the zero value of time.Time is simply:
time.Time{}
This represents the earliest representable time in Go's time.Time type:
0001-01-01 00:: + UTC
Mental Model
Think of a time.Time value like a printed form with date fields on it.
- A pointer can be
nil, which is like saying: “there is no form at all.” - A struct value like
time.Timealways exists as a full form. - Its zero value is like a blank default form that has not been filled in with a real date.
So when you have a plain time.Time, you cannot say “no form” with nil. You can only use its default blank value:
time.Time{}
If you really need the idea of “no time at all,” then you can use a pointer:
var t *time.Time = nil
But in normal Go code, returning time.Time{} plus an error is usually the simplest and most idiomatic choice.
Syntax and Examples
The core syntax is straightforward.
Zero value of time.Time
var t time.Time
fmt.Println(t)
Because t was declared but not assigned, it gets the zero value automatically.
Explicit zero value
zero := time.Time{}
fmt.Println(zero)
This creates the same zero value explicitly.
Returning zero value from a function
package main
import (
"errors"
"time"
)
func getTimestamp(ok bool) (time.Time, error) {
if !ok {
return time.Time{}, errors.New("timestamp not available")
}
return time.Now(), nil
}
Here:
time.Now()is returned when successfultime.Time{}is returned when there is an error
Checking whether a time is zero
Step by Step Execution
Consider this example:
package main
import (
"errors"
"fmt"
"time"
)
func parseDeadline(input string) (time.Time, error) {
if input == "" {
return time.Time{}, errors.New("empty deadline")
}
return time.Parse("2006-01-02", input)
}
func main() {
deadline, err := parseDeadline("")
fmt.Println("deadline:", deadline)
fmt.Println("is zero:", deadline.IsZero())
fmt.Println("error:", err)
}
What happens step by step
main()callsparseDeadline("").- Inside
parseDeadline, the input is an empty string. - The condition
input == ""is true. - The function returns:
time.Time{}as the zerotime.Time
Real World Use Cases
time.Time zero values appear in many practical Go programs.
1. Function results with errors
A function that fetches a timestamp from a database or API may return:
return time.Time{}, err
if the lookup fails.
2. Optional timestamps
You may have fields such as:
CreatedAtUpdatedAtDeletedAtPublishedAt
A zero time.Time can mean the timestamp has not been set yet.
3. Form or config validation
If a date was not provided, a zero time.Time may represent “missing input” until validation decides whether that is allowed.
4. Scheduling systems
A job runner might use zero time to mean:
- never run
- not scheduled yet
- retry time not calculated yet
5. Parsing and transformation pipelines
When converting raw strings into typed data, zero time may temporarily represent an unset or failed value until errors are handled.
Real Codebase Usage
In real Go codebases, developers usually use time.Time in a few common patterns.
Guard clause with zero return
func startTimeFromConfig(cfg string) (time.Time, error) {
if cfg == "" {
return time.Time{}, errors.New("missing config value")
}
return time.Parse(time.RFC3339, cfg)
}
This is a classic guard clause: fail early, return the zero value, and report the error.
Validation with IsZero()
func canPublish(publishedAt time.Time) bool {
return !publishedAt.IsZero()
}
This is clearer than comparing formatted strings or manually checking fields.
Optional field design
Sometimes developers choose between:
time.Timewhen a value should always exist eventually*time.Timewhennilshould explicitly mean “missing” or “not set”
Common Mistakes
Here are common mistakes beginners make with time.Time.
Mistake 1: Returning nil for a non-pointer type
Broken code:
func getTime() (time.Time, error) {
return nil, errors.New("failed")
}
Why it fails:
time.Timeis a struct, not a pointer- only some types can be
nil
Correct version:
func getTime() (time.Time, error) {
return time.Time{}, errors.New("failed")
}
Mistake 2: Comparing to nil
Broken code:
var t time.Time
if t == nil {
fmt.Println("unset")
}
Why it fails:
Comparisons
Here is how time.Time compares with related choices.
| Concept | Can be nil? | Zero/default value | Best use |
|---|---|---|---|
time.Time | No | time.Time{} | Regular timestamps that always have a value type |
*time.Time | Yes | nil | Optional timestamps where absence matters |
string date like "2024-01-01" | No | "" | Raw input or display text, not ideal for date logic |
vs
Cheat Sheet
// Zero value of time.Time
var t time.Time
// Explicit zero value
zero := time.Time{}
// Return zero value on error
return time.Time{}, err
// Check whether a time is zero
if t.IsZero() {
// t has not been set
}
Rules to remember
time.Timeis a struct, so it cannot benil*time.Timeis a pointer, so it can benil- The zero value of
time.Timeistime.Time{} - The printed zero time is usually
0001-01-01 00:00:00 +0000 UTC - Use
IsZero()to test for the zero value - In
(time.Time, error)functions, returntime.Time{}, erron failure
Quick decision guide
- Need a normal timestamp value? Use
time.Time - Need to represent missing time with
nil? Use*time.Time
FAQ
Why can't I return nil for time.Time in Go?
Because time.Time is a struct value, not a pointer. Only certain types such as pointers, slices, maps, interfaces, channels, and functions can be nil.
What is the zero value of time.Time?
The zero value is:
time.Time{}
It prints as 0001-01-01 00:00:00 +0000 UTC.
How do I check if a time.Time is unset?
Use:
t.IsZero()
This is the standard and most readable approach.
Should I use time.Time{} or time.Now() as a default?
Use time.Time{} only when you mean “no real time set yet.” Use time.Now() when you want the current time.
When should I use *time.Time instead of time.Time?
Mini Project
Description
Build a small Go program that simulates loading an event start time from user input. This project demonstrates how to return a zero time.Time on failure, how to detect unset times with IsZero(), and how to handle successful and failed parsing clearly.
Goal
Create a program that parses an optional date string and reports whether it received a valid time or a zero time.Time.
Requirements
- Write a function that accepts a date string and returns
(time.Time, error). - Return
time.Time{}when the input is empty or invalid. - Parse valid input using the layout
2006-01-02. - In
main, test both a valid date and an empty string. - Print whether each returned time is zero using
IsZero().
Keep learning
Related questions
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.
Blank Identifier Imports in Go: What `_` Means in an Import Statement
Learn what `_` means in a Go import, why blank identifier imports run package init code, and when to use them safely.
Calling Functions Across Files in the Same Go Package
Learn how Go uses packages across multiple files, why functions may appear undefined, and how to organize code correctly.