quando – Intuitive Date Calculations for Go
quando is a Go library for intuitive date calculations with a fluent API. It provides a simple, chainable interface for date operations while being immutable, having zero external dependencies, and handling edge cases like month-end dates and daylight saving time transitions correctly.
What is quando?
Date calculations in Go's standard time package can be verbose and error-prone. Simple operations like "add 3 months" or "go to the last day of the month" require careful handling of edge cases. quando provides a fluent, immutable API that makes date operations more intuitive and less error-prone while maintaining full compatibility with Go's time.Time.
Why this library?
I frequently work with date calculations in business applications – subscription periods, billing cycles, report ranges, and scheduling tasks. The standard library's approach often leads to repetitive code and subtle bugs with month boundaries.
The practical trigger was my existing DatesAPI web service – a small demo project that provides formatted date strings via a simple API. When I wanted to add more advanced date operations to it, I realized I needed a solid foundation for date calculations first. Rather than building that logic directly into the service, I extracted it into a standalone library. quando was born from the need for a simple, chainable API that handles edge cases correctly and makes the code more readable.
Installation
go get code.beautifulmachines.dev/jakoubek/quando
Quick Start
package main
import (
"fmt"
"time"
"code.beautifulmachines.dev/jakoubek/quando"
)
func main() {
// Start with today's date
today := quando.From(time.Now())
// Add 3 months and 5 days
future := today.Add(3, quando.Months).Add(5, quando.Days)
fmt.Printf("In 3 months and 5 days: %s\n", future.Time())
// Subtract 2 weeks
past := today.Sub(2, quando.Weeks)
fmt.Printf("2 weeks ago: %s\n", past.Time())
// Go to the last day of the month
monthEnd := today.EndOf(quando.Months)
fmt.Printf("End of this month: %s\n", monthEnd.Time())
// Chain operations
result := today.
Add(1, quando.Months).
StartOf(quando.Months).
Add(14, quando.Days)
fmt.Printf("15th of next month: %s\n", result.Time())
}
Features
Fluent API
quando provides a chainable API that makes date calculations readable and expressive:
// Traditional approach with time package
t := time.Now()
t = t.AddDate(0, 3, 0) // Add 3 months
t = t.AddDate(0, 0, 7) // Add 7 days
// quando approach
result := quando.From(time.Now()).
Add(3, quando.Months).
Add(7, quando.Days).
Time()
Month-End Awareness
quando handles month-end dates correctly, which is crucial for business calculations:
// January 31 + 1 month = February 28/29 (last day of Feb)
jan31 := quando.From(time.Date(2024, 1, 31, 0, 0, 0, 0, time.UTC))
result := jan31.Add(1, quando.Months)
fmt.Println(result.Time()) // 2024-02-29 (leap year)
// February 29 + 1 year = February 28 (non-leap year)
feb29 := quando.From(time.Date(2024, 2, 29, 0, 0, 0, 0, time.UTC))
result = feb29.Add(1, quando.Years)
fmt.Println(result.Time()) // 2025-02-28
Immutability
All operations return new instances, making quando safe for concurrent use:
start := quando.From(time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC))
end := start.Add(3, quando.Months)
// start is unchanged
fmt.Println(start.Time()) // 2024-01-15
fmt.Println(end.Time()) // 2024-04-15
Start and End of Period
Navigate to the beginning or end of various time periods:
now := quando.Now()
// Month boundaries
monthStart := now.StartOf(quando.Months)
monthEnd := now.EndOf(quando.Months)
// Week boundaries (Monday-based)
weekStart := now.StartOf(quando.Weeks)
weekEnd := now.EndOf(quando.Weeks)
// Year boundaries
yearStart := now.StartOf(quando.Years)
yearEnd := now.EndOf(quando.Years)
// Quarter boundaries
quarterStart := now.StartOf(quando.Quarters)
quarterEnd := now.EndOf(quando.Quarters)
Day Navigation
Find the next or previous occurrence of a weekday:
now := quando.Now()
// Next Friday
nextFriday := now.Next(time.Friday)
// Previous Monday
lastMonday := now.Prev(time.Monday)
Human-Readable Differences
Calculate and display durations between dates:
start := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
end := time.Date(2026, 3, 15, 0, 0, 0, 0, time.UTC)
duration := quando.Diff(start, end)
fmt.Println(duration.Human()) // "2 years, 2 months"
fmt.Println(duration.Months()) // 26
fmt.Println(duration.Days()) // 804
Parsing and Formatting
Automatic format detection and multilingual formatting:
// Automatic format detection
date, _ := quando.Parse("2026-02-09") // ISO format
date, _ = quando.Parse("09.02.2026") // EU format
// Relative dates via ParseRelative
date, _ = quando.ParseRelative("+3 days") // 3 days from now
date, _ = quando.ParseRelative("-1 week") // 1 week ago
date, _ = quando.ParseRelative("tomorrow") // Tomorrow
// Formatting
date.Format(quando.ISO) // "2026-02-09"
date.Format(quando.Long) // "February 9, 2026"
// Multilingual
dateDE := quando.Now().WithLang(quando.DE)
fmt.Println(dateDE.Format(quando.Long)) // "9. Februar 2026"
Date Inspection
date := quando.Now()
week := date.WeekNumber() // ISO 8601 week number
quarter := date.Quarter() // 1-4
dayOfYear := date.DayOfYear() // 1-366
Zero Dependencies
quando uses only Go's standard library (time package). No external dependencies means:
- Smaller binary size
- Faster builds
- No dependency conflicts
- No supply chain security concerns
Requirements
- Go 1.22 or higher
Links
- Forgejo: code.beautifulmachines.dev/jakoubek/quando
- GitHub Mirror: github.com/jakoubek/quando
- Go Package: pkg.go.dev/code.beautifulmachines.dev/jakoubek/quando
License
MIT License – see LICENSE.