The Go time
package provides comprehensive functionality for measuring and displaying time. It follows a design philosophy that prioritizes accuracy, timezone awareness, and thread safety. The package uses a Gregorian calendar with no leap seconds and distinguishes between "wall clock" time (for telling time) and "monotonic clock" time (for measuring time intervals).
Key Design Principles
- Immutability:
time.Time
values are immutable; operations return new instances - Thread Safety: All time operations are safe for concurrent use
- Timezone Awareness: Every
time.Time
has an associatedtime.Location
- Precision: Nanosecond precision for all time operations
- Monotonic Clock Support: Robust time measurement unaffected by clock adjustments
Core Types
time.Time
Represents an instant in time with nanosecond precision. Contains both wall clock and optional monotonic clock readings.
type Time struct {
// Internal fields are unexported
// wall and ext encode the wall time and optional monotonic reading
}
Key characteristics:
- Zero value: January 1, year 1, 00:00:00.000000000 UTC
- Immutable: methods return new
Time
values - Thread-safe for concurrent reads
- Contains both wall clock and optional monotonic clock readings
time.Duration
Represents elapsed time as an int64
nanosecond count, supporting approximately 290 years.
type Duration int64
// Predefined constants
const (
Nanosecond Duration = 1
Microsecond = 1000 * Nanosecond
Millisecond = 1000 * Microsecond
Second = 1000 * Millisecond
Minute = 60 * Second
Hour = 60 * Minute
)
time.Location
Represents a time zone, mapping time instants to the zone in use at that time.
var (
UTC *Location = &utcLoc // UTC timezone
Local *Location = &localLoc // System's local timezone
)
Time Creation and Current Time
Learn how to get the current time and create specific timestamps using Go’s built-in functions.
Getting Current Time
// Get current time with monotonic clock reading
now := time.Now()
fmt.Println(now) // 2025-08-27 14:55:00.123456789 +0700 +07 m=+0.000000001
// Get current time in specific timezone
loc, _ := time.LoadLocation("America/New_York")
nowNY := time.Now().In(loc)
Creating Specific Times
// Create specific time
t := time.Date(2025, time.August, 27, 14, 30, 0, 0, time.UTC)
// From Unix timestamp
unixTime := time.Unix(1693140600, 0) // seconds since epoch
unixMilli := time.UnixMilli(1693140600000) // milliseconds since epoch
unixMicro := time.UnixMicro(1693140600000000) // microseconds since epoch
Zero Time Detection
Detect and handle zero-value time.Time
objects safely in your code.
var t time.Time
if t.IsZero() {
fmt.Println("Time is zero value")
}
Golang Time Format and Parse
Go uses a unique reference time approach for formatting: Mon Jan 2 15:04:05 MST 2006 (which can be remembered as 01/02 03:04:05PM '06 -0700).
Predefined Layout Constants
const (
Layout = "01/02 03:04:05PM '06 -0700"
ANSIC = "Mon Jan _2 15:04:05 2006"
UnixDate = "Mon Jan _2 15:04:05 MST 2006"
RubyDate = "Mon Jan 02 15:04:05 -0700 2006"
RFC822 = "02 Jan 06 15:04 MST"
RFC822Z = "02 Jan 06 15:04 -0700"
RFC850 = "Monday, 02-Jan-06 15:04:05 MST"
RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST"
RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700"
RFC3339 = "2006-01-02T15:04:05Z07:00"
RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
Kitchen = "3:04PM"
DateTime = "2006-01-02 15:04:05"
DateOnly = "2006-01-02"
TimeOnly = "15:04:05"
)
Formatting Examples
now := time.Now()
// Using predefined constants
fmt.Println(now.Format(time.RFC3339)) // 2025-08-27T14:55:00Z
fmt.Println(now.Format(time.Kitchen)) // 2:55PM
fmt.Println(now.Format(time.DateOnly)) // 2025-08-27
// Custom formatting
fmt.Println(now.Format("2006-01-02 15:04:05")) // 2025-08-27 14:55:00
fmt.Println(now.Format("Jan 2, 2006")) // Aug 27, 2025
fmt.Println(now.Format("Monday, January 2")) // Tuesday, August 27
Parsing Examples
// Parse with predefined layout
t1, err := time.Parse(time.RFC3339, "2025-08-27T14:55:00Z")
if err != nil {
log.Fatal(err)
}
// Parse with custom layout
t2, err := time.Parse("2006-01-02 15:04:05", "2025-08-27 14:55:00")
if err != nil {
log.Fatal(err)
}
// Parse in specific location
loc, _ := time.LoadLocation("America/New_York")
t3, err := time.ParseInLocation("2006-01-02 15:04:05", "2025-08-27 14:55:00", loc)
Layout Reference Guide
Component | Options | Example |
---|---|---|
Year | "2006", "06" | 2025, 25 |
Month | "Jan", "January", "01", "1" | Aug, August, 08, 8 |
Day | "2", "_2", "02" | 27, 27, 27 |
Weekday | "Mon", "Monday" | Tue, Tuesday |
Hour | "15", "3", "03" | 14, 2, 02 |
Minute | "4", "04" | 5, 05 |
Second | "5", "05" | 0, 00 |
Timezone | "-0700", "MST", "Z07:00" | +0700, +07, +07:00 |
Golang Time Duration Operations
Creating Durations
// From constants
d1 := 5 * time.Second
d2 := 2 * time.Minute + 30 * time.Second
d3 := time.Hour + 15 * time.Minute
// From string parsing
d4, err := time.ParseDuration("1h30m45s")
d5, err := time.ParseDuration("100ms")
d6, err := time.ParseDuration("2.5h")
Duration Methods
d := 90 * time.Minute
// Convert to different units
fmt.Println(d.Hours()) // 1.5
fmt.Println(d.Minutes()) // 90
fmt.Println(d.Seconds()) // 5400
fmt.Println(d.Milliseconds()) // 5400000
fmt.Println(d.Microseconds()) // 5400000000
fmt.Println(d.Nanoseconds()) // 5400000000000
// String representation
fmt.Println(d.String()) // 1h30m0s
// Absolute value
neg := -5 * time.Second
fmt.Println(neg.Abs()) // 5s
// Rounding and truncation
d := 1*time.Hour + 15*time.Minute + 30*time.Second + 918*time.Millisecond
fmt.Println(d.Round(time.Minute)) // 1h16m0s
fmt.Println(d.Truncate(time.Minute)) // 1h15m0s
Time Zone Handling
Loading Time Zones
// Load specific timezone
ny, err := time.LoadLocation("America/New_York")
if err != nil {
log.Fatal(err)
}
// Load UTC and Local
utc := time.UTC
local := time.Local
// Create fixed timezone
// FixedZone(name, offset_in_seconds)
customTZ := time.FixedZone("GMT+8", 8*60*60)
Converting Between Time Zones
// Create time in UTC
utcTime := time.Date(2025, 8, 27, 14, 30, 0, 0, time.UTC)
// Convert to different timezones
ny, _ := time.LoadLocation("America/New_York")
tokyo, _ := time.LoadLocation("Asia/Tokyo")
nyTime := utcTime.In(ny)
tokyoTime := utcTime.In(tokyo)
fmt.Println("UTC: ", utcTime.Format(time.RFC3339))
fmt.Println("NY: ", nyTime.Format(time.RFC3339))
fmt.Println("Tokyo: ", tokyoTime.Format(time.RFC3339))
Working with DST
// Check if time is in DST
ny, _ := time.LoadLocation("America/New_York")
summer := time.Date(2025, 7, 15, 12, 0, 0, 0, ny)
winter := time.Date(2025, 12, 15, 12, 0, 0, 0, ny)
fmt.Println("Summer DST:", summer.IsDST()) // true
fmt.Println("Winter DST:", winter.IsDST()) // false
// Get timezone information
name, offset := summer.Zone()
fmt.Printf("Zone: %s, Offset: %d seconds\\n", name, offset)
Time Comparison and Arithmetic
Compare two times or calculate differences using Go’s intuitive API.
Time Comparison
t1 := time.Date(2025, 8, 27, 10, 0, 0, 0, time.UTC)
t2 := time.Date(2025, 8, 27, 11, 0, 0, 0, time.UTC)
// Comparison methods
fmt.Println(t1.Before(t2)) // true
fmt.Println(t1.After(t2)) // false
fmt.Println(t1.Equal(t2)) // false
// Compare method (Go 1.20+)
fmt.Println(t1.Compare(t2)) // -1 (t1 < t2)
fmt.Println(t2.Compare(t1)) // 1 (t2 > t1)
fmt.Println(t1.Compare(t1)) // 0 (t1 == t1)
// Prefer Equal() over == for reliable comparison
fmt.Println(t1 == t2) // may be unreliable due to monotonic clock
fmt.Println(t1.Equal(t2)) // reliable comparison
Time Arithmetic
now := time.Now()
// Add duration
future := now.Add(2 * time.Hour)
past := now.Add(-30 * time.Minute)
// Add date components
nextYear := now.AddDate(1, 0, 0) // +1 year
nextMonth := now.AddDate(0, 1, 0) // +1 month
tomorrow := now.AddDate(0, 0, 1) // +1 day
// Subtract times to get duration
elapsed := future.Sub(now)
fmt.Println(elapsed) // 2h0m0s
// Helper functions
since := time.Since(now) // time.Now().Sub(now)
until := time.Until(future) // future.Sub(time.Now())
Rounding and Truncation
now := time.Now()
// Round to nearest hour
rounded := now.Round(time.Hour)
// Truncate to start of hour
truncated := now.Truncate(time.Hour)
// Strip monotonic clock reading
stripped := now.Round(0)
Monotonic Clock
The monotonic clock provides accurate time measurement unaffected by system clock adjustments.
Understanding Monotonic Clock
// Times from Now() include monotonic reading
start := time.Now()
time.Sleep(100 * time.Millisecond)
end := time.Now()
// Duration calculation uses monotonic clock if available
elapsed := end.Sub(start) // Accurate even if system clock changes
// Monotonic clock visible in String() output
fmt.Println(start) // includes "m=+0.000000001"
Operations That Strip Monotonic Clock
now := time.Now() // Has monotonic reading
// These operations strip monotonic reading:
utc := now.UTC() // Timezone conversion
local := now.Local() // Timezone conversion
ny := now.In(nyLocation) // Timezone conversion
rounded := now.Round(time.Hour) // Rounding (when d > 0)
truncated := now.Truncate(time.Hour) // Truncation (when d > 0)
dated := now.AddDate(0, 0, 1) // Date arithmetic
// Explicitly strip monotonic reading
stripped := now.Round(0)
Monotonic Clock Best Practices
- Use for measuring elapsed time:
elapsed := time.Since(start)
- Don't rely on monotonic clock for serialization
- Use
t.Equal(u)
instead oft == u
for comparisons - Strip monotonic reading when using as map keys:
t.Round(0)
Timers and Tickers
Timers
// Simple timer for one-time events
timer := time.NewTimer(5 * time.Second)
<-timer.C // Wait for timer to fire
fmt.Println("Timer fired!")
// Using After for simple delays
select {
case <-time.After(5 * time.Second):
fmt.Println("Timeout!")
case data := <-dataChannel:
fmt.Println("Received:", data)
}
// Timer with function callback
timer := time.AfterFunc(5*time.Second, func() {
fmt.Println("Timer callback executed!")
})
// Stop timer if needed
if !timer.Stop() {
// Timer already fired or was stopped
}
// Reset timer
timer.Reset(10 * time.Second)
Tickers
// Create ticker for repeated events
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop() // Always stop tickers to prevent leaks
for i := 0; i < 5; i++ {
select {
case t := <-ticker.C:
fmt.Printf("Tick at %v\\n", t)
}
}
// Using Tick for simple repeating events
c := time.Tick(1 * time.Second) // Returns nil if d <= 0
for t := range c {
fmt.Printf("Tick: %v\\n", t)
// Note: This creates an unstoppable ticker
}
// Reset ticker period
ticker.Reset(2 * time.Second)
Timer Safety and Performance
// Safe timer pattern
func safeTimerPattern() {
timer := time.NewTimer(5 * time.Second)
defer func() {
if !timer.Stop() {
// Drain channel if timer fired
select {
case <-timer.C:
default:
}
}
}()
select {
case <-timer.C:
fmt.Println("Timer fired")
case <-someOtherChannel():
fmt.Println("Other event occurred")
return
}
}
>> Read more: A Comprehensive Guide to Concurrency in Golang
Performance Considerations
Timer Resolution
- Unix systems: ~1ms resolution
- Windows 1803+: ~0.5ms resolution
- Older Windows: ~16ms resolution (can be improved with
TimeBeginPeriod
)
Memory and GC
// As of Go 1.23, timers and tickers are automatically GC'd
// No need to explicitly stop them for GC purposes
// However, still stop for logic purposes
ticker := time.NewTicker(time.Second)
defer ticker.Stop() // Good practice for clarity
// Avoid creating many short-lived timers in hot paths
// Consider using a single timer pool or time wheel
Benchmark Example
func BenchmarkTimeOperations(b *testing.B) {
now := time.Now()
b.Run("Now", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = time.Now()
}
})
b.Run("Format", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = now.Format(time.RFC3339)
}
})
b.Run("Parse", func(b *testing.B) {
timeStr := "2025-08-27T14:55:00Z"
for i := 0; i < b.N; i++ {
_, _ = time.Parse(time.RFC3339, timeStr)
}
})
}
Best Practices
Always Handle Parsing Errors
// Bad
t, _ := time.Parse(time.RFC3339, userInput)
// Good
t, err := time.Parse(time.RFC3339, userInput)
if err != nil {
return fmt.Errorf("invalid time format: %w", err)
}
Use Appropriate Layout Constants
// Bad - magic strings
t.Format("2006-01-02T15:04:05Z07:00")
// Good - use constants
t.Format(time.RFC3339)
// Good - define custom constants
const CustomLayout = "2006-01-02 15:04:05"
t.Format(CustomLayout)
Be Explicit About Time Zones
// Bad - ambiguous timezone
t, _ := time.Parse("2006-01-02 15:04:05", "2025-08-27 14:55:00")
// Good - explicit timezone
loc, _ := time.LoadLocation("America/New_York")
t, _ := time.ParseInLocation("2006-01-02 15:04:05", "2025-08-27 14:55:00", loc)
// Or use timezone-aware format
t, _ := time.Parse(time.RFC3339, "2025-08-27T14:55:00Z")
Use time.Equal() for Comparisons
// Bad - may fail due to monotonic clock differences
if t1 == t2 {
// ...
}
// Good - handles monotonic clock properly
if t1.Equal(t2) {
// ...
}
Measure Elapsed Time Correctly
// Good - uses monotonic clock
start := time.Now()
doWork()
elapsed := time.Since(start)
// Also good
start := time.Now()
doWork()
elapsed := time.Now().Sub(start)
Store UTC in Databases
// Good practice - store UTC, display in local timezone
func storeTime(t time.Time) {
utcTime := t.UTC()
// Store utcTime in database
}
func displayTime(utcTime time.Time, userTZ string) string {
loc, _ := time.LoadLocation(userTZ)
localTime := utcTime.In(loc)
return localTime.Format("2006-01-02 15:04:05 MST")
}
Handle Zero Times
func processTime(t time.Time) error {
if t.IsZero() {
return errors.New("time cannot be zero")
}
// Process non-zero time
return nil
}
Use Context for Timeouts
func processWithTimeout(ctx context.Context) error {
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
// Use ctx for operations that should respect timeout
return doWorkWithContext(ctx)
}
Common Pitfalls and Solutions
Layout String Confusion
// Common mistake - using wrong reference time
t.Format("YYYY-MM-DD") // Wrong! Go doesn't use these placeholders
// Correct - use Go's reference time
t.Format("2006-01-02") // Correct
Time Zone Loading Errors
// Bad - ignoring errors
loc, _ := time.LoadLocation("Invalid/Timezone")
// Good - handle errors
loc, err := time.LoadLocation("America/New_York")
if err != nil {
log.Printf("Failed to load timezone: %v", err)
loc = time.UTC // fallback to UTC
}
Timer/Ticker Resource Leaks
// Bad - potential resource leak
func badTicker() {
ticker := time.NewTicker(time.Second)
for range ticker.C {
// Process tick
if someCondition {
return // Ticker never stopped!
}
}
}
// Good - always stop tickers
func goodTicker() {
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
// Process tick
if someCondition {
return
}
}
}
}
Monotonic Clock Stripping
// Problem - using time as map key
times := make(map[time.Time]string)
t1 := time.Now()
t2 := time.Date(2025, 8, 27, 14, 55, 0, 0, time.UTC)
// t1 and t2 might represent same time but have different monotonic readings
// Solution - strip monotonic reading
times[t1.Round(0)] = "value1"
times[t2.Round(0)] = "value2"
Parsing Ambiguous Formats
// Problem - ambiguous date format
dateStr := "01/02/2006" // Is this Jan 2 or Feb 1?
// Solution - use unambiguous formats
// Use ISO 8601 format
dateStr := "2006-01-02"
t, err := time.Parse("2006-01-02", dateStr)
// Or be explicit about format
t, err := time.Parse("01/02/2006", "01/02/2006") // MM/DD/YYYY
t, err := time.Parse("02/01/2006", "01/02/2006") // DD/MM/YYYY
Examples and Use Cases
API Rate Limiting
type RateLimiter struct {
tokens int
maxTokens int
interval time.Duration
lastRefill time.Time
mu sync.Mutex
}
func NewRateLimiter(maxTokens int, interval time.Duration) *RateLimiter {
return &RateLimiter{
tokens: maxTokens,
maxTokens: maxTokens,
interval: interval,
lastRefill: time.Now(),
}
}
func (rl *RateLimiter) Allow() bool {
rl.mu.Lock()
defer rl.mu.Unlock()
now := time.Now()
elapsed := now.Sub(rl.lastRefill)
if elapsed >= rl.interval {
rl.tokens = rl.maxTokens
rl.lastRefill = now
}
if rl.tokens > 0 {
rl.tokens--
return true
}
return false
}
Retry with Exponential Backoff
func retryWithBackoff(operation func() error, maxRetries int) error {
baseDelay := 100 * time.Millisecond
maxDelay := 10 * time.Second
for attempt := 0; attempt < maxRetries; attempt++ {
if err := operation(); err == nil {
return nil
}
if attempt == maxRetries-1 {
return fmt.Errorf("operation failed after %d attempts", maxRetries)
}
// Calculate delay with exponential backoff
delay := time.Duration(float64(baseDelay) * math.Pow(2, float64(attempt)))
if delay > maxDelay {
delay = maxDelay
}
time.Sleep(delay)
}
return nil
}
Scheduled Task Runner
type Scheduler struct {
tasks map[string]*ScheduledTask
mu sync.RWMutex
stop chan struct{}
}
type ScheduledTask struct {
Name string
Interval time.Duration
Fn func()
ticker *time.Ticker
}
func NewScheduler() *Scheduler {
return &Scheduler{
tasks: make(map[string]*ScheduledTask),
stop: make(chan struct{}),
}
}
func (s *Scheduler) AddTask(name string, interval time.Duration, fn func()) {
s.mu.Lock()
defer s.mu.Unlock()
task := &ScheduledTask{
Name: name,
Interval: interval,
Fn: fn,
ticker: time.NewTicker(interval),
}
s.tasks[name] = task
go func() {
for {
select {
case <-task.ticker.C:
fn()
case <-s.stop:
return
}
}
}()
}
func (s *Scheduler) Stop() {
s.mu.Lock()
defer s.mu.Unlock()
close(s.stop)
for _, task := range s.tasks {
task.ticker.Stop()
}
}
Time-based Cache with TTL
type TTLCache struct {
items map[string]*cacheItem
mu sync.RWMutex
}
type cacheItem struct {
value interface{}
expiresAt time.Time
}
func NewTTLCache() *TTLCache {
cache := &TTLCache{
items: make(map[string]*cacheItem),
}
// Start cleanup goroutine
go cache.cleanup()
return cache
}
func (c *TTLCache) Set(key string, value interface{}, ttl time.Duration) {
c.mu.Lock()
defer c.mu.Unlock()
c.items[key] = &cacheItem{
value: value,
expiresAt: time.Now().Add(ttl),
}
}
func (c *TTLCache) Get(key string) (interface{}, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
item, exists := c.items[key]
if !exists {
return nil, false
}
if time.Now().After(item.expiresAt) {
return nil, false
}
return item.value, true
}
func (c *TTLCache) cleanup() {
ticker := time.NewTicker(1 * time.Minute)
defer ticker.Stop()
for range ticker.C {
c.mu.Lock()
now := time.Now()
for key, item := range c.items {
if now.After(item.expiresAt) {
delete(c.items, key)
}
}
c.mu.Unlock()
}
}
Business Hours Checker
type BusinessHours struct {
timezone *time.Location
openTime time.Duration // Duration since midnight
closeTime time.Duration // Duration since midnight
workdays []time.Weekday
holidays []time.Time
}
func NewBusinessHours(timezone string) (*BusinessHours, error) {
loc, err := time.LoadLocation(timezone)
if err != nil {
return nil, err
}
return &BusinessHours{
timezone: loc,
openTime: 9 * time.Hour, // 9 AM
closeTime: 17 * time.Hour, // 5 PM
workdays: []time.Weekday{
time.Monday, time.Tuesday, time.Wednesday,
time.Thursday, time.Friday,
},
holidays: []time.Time{},
}, nil
}
func (bh *BusinessHours) IsBusinessTime(t time.Time) bool {
// Convert to business timezone
localTime := t.In(bh.timezone)
// Check if it's a workday
isWorkday := false
for _, day := range bh.workdays {
if localTime.Weekday() == day {
isWorkday = true
break
}
}
if !isWorkday {
return false
}
// Check if it's a holiday
for _, holiday := range bh.holidays {
if isSameDate(localTime, holiday.In(bh.timezone)) {
return false
}
}
// Check business hours
todayStart := time.Date(localTime.Year(), localTime.Month(), localTime.Day(),
0, 0, 0, 0, bh.timezone)
openTime := todayStart.Add(bh.openTime)
closeTime := todayStart.Add(bh.closeTime)
return localTime.After(openTime) && localTime.Before(closeTime)
}
func isSameDate(t1, t2 time.Time) bool {
y1, m1, d1 := t1.Date()
y2, m2, d2 := t2.Date()
return y1 == y2 && m1 == m2 && d1 == d2
}
Performance Measurement
type PerformanceTracker struct {
measurements map[string][]time.Duration
mu sync.Mutex
}
func NewPerformanceTracker() *PerformanceTracker {
return &PerformanceTracker{
measurements: make(map[string][]time.Duration),
}
}
func (pt *PerformanceTracker) Track(name string, fn func()) {
start := time.Now()
fn()
elapsed := time.Since(start)
pt.mu.Lock()
pt.measurements[name] = append(pt.measurements[name], elapsed)
pt.mu.Unlock()
}
func (pt *PerformanceTracker) Stats(name string) (avg, min, max time.Duration, count int) {
pt.mu.Lock()
durations := pt.measurements[name]
pt.mu.Unlock()
if len(durations) == 0 {
return 0, 0, 0, 0
}
var total time.Duration
min = durations[0]
max = durations[0]
for _, d := range durations {
total += d
if d < min {
min = d
}
if d > max {
max = d
}
}
avg = total / time.Duration(len(durations))
count = len(durations)
return
}
Conclusion
This comprehensive guide covers all essential aspects of Go's time package, from basic usage to advanced patterns and best practices. The examples demonstrate real-world applications and help avoid common pitfalls when working with time in Go applications.
>>> Follow and Contact Relia Software for more information!
- golang
- coding