Changed module path from code.beautifulmachines.dev/quando to code.beautifulmachines.dev/jakoubek/quando to include user namespace. Updated: - go.mod: module declaration - example_test.go: import path |
||
|---|---|---|
| .beads | ||
| .github/workflows | ||
| .gitattributes | ||
| .gitignore | ||
| AGENTS.md | ||
| CLAUDE.md | ||
| clock.go | ||
| clock_test.go | ||
| date.go | ||
| date_test.go | ||
| example_test.go | ||
| go.mod | ||
| LICENSE | ||
| PRD.md | ||
| quando.go | ||
| README.md | ||
| snap.go | ||
| snap_test.go | ||
| unit.go | ||
| unit_test.go | ||
quando
Intuitive and idiomatic date calculations for Go
quando is a standalone Go library for complex date operations that are cumbersome or impossible with the Go standard library alone. It provides a fluent API for date arithmetic, parsing, formatting, and timezone-aware calculations.
Features
- Fluent API: Chain operations naturally:
quando.Now().Add(2, Months).StartOf(Week) - Month-End Aware: Handles edge cases like
Jan 31 + 1 month = Feb 28 - DST Safe: Calendar-based arithmetic (not clock-based)
- Zero Dependencies: Only Go stdlib
- Immutable: Thread-safe by design
- i18n Ready: Multilingual formatting (EN, DE in Phase 1)
- Testable: Built-in Clock abstraction for deterministic tests
Installation
go get code.beautifulmachines.dev/quando
Quick Start
import "code.beautifulmachines.dev/quando"
// Get current date
now := quando.Now()
// Date arithmetic with month-end handling
date := quando.From(time.Date(2026, 1, 31, 0, 0, 0, 0, time.UTC))
result := date.Add(1, quando.Months) // Feb 28, 2026 (not overflow)
// Snap to boundaries
monday := quando.Now().StartOf(quando.Week) // This week's Monday 00:00
endOfMonth := quando.Now().EndOf(quando.Month) // Last day of month 23:59:59
// Next/Previous dates
nextFriday := quando.Now().Next(time.Friday)
prevMonday := quando.Now().Prev(time.Monday)
// Differences
duration := quando.Diff(startDate, endDate)
months := duration.Months()
humanReadable := duration.Human() // "2 years, 3 months, 5 days"
// Parsing (automatic format detection)
date, err := quando.Parse("2026-02-09")
date, err := quando.Parse("09.02.2026") // EU format
date, err := quando.Parse("3 days ago") // Relative
// Formatting
date.Format(quando.ISO) // "2026-02-09"
date.Format(quando.Long) // "February 9, 2026"
date.FormatLayout("02 Jan") // "09 Feb"
// Timezone conversion
utc := date.InTimezone("UTC")
berlin := date.InTimezone("Europe/Berlin")
// Date inspection
week := date.WeekNumber() // ISO 8601 week number
quarter := date.Quarter() // 1-4
dayOfYear := date.DayOfYear() // 1-366
Core Concepts
Month-End Overflow Handling
When adding months, if the target day doesn't exist, quando snaps to the last valid day:
jan31 := quando.From(time.Date(2026, 1, 31, 0, 0, 0, 0, time.UTC))
feb28 := jan31.Add(1, quando.Months) // Feb 28, not March 3
DST-Aware Arithmetic
Adding days means "same time on next calendar day", not "24 hours later":
// During DST transition (23-hour day)
date := quando.From(time.Date(2026, 3, 31, 2, 0, 0, 0, cetLocation))
next := date.Add(1, quando.Days) // April 1, 2:00 CEST (not 3:00)
Immutability
All operations return new instances. Original values are never modified:
original := quando.Now()
modified := original.Add(1, quando.Days)
// original is unchanged
Testing
quando provides a Clock interface for deterministic tests:
// In production
date := quando.Now()
// In tests
fixedTime := time.Date(2026, 2, 9, 12, 0, 0, 0, time.UTC)
clock := quando.NewFixedClock(fixedTime)
date := clock.Now() // Always returns Feb 9, 2026
Requirements
- Go 1.22 or later
- No external dependencies
Documentation
Full documentation available at: pkg.go.dev/code.beautifulmachines.dev/quando
License
MIT License - see LICENSE file for details.
Contributing
Contributions welcome! Please ensure:
- Tests pass (
go test ./...) - Code is formatted (
go fmt ./...) - No vet warnings (
go vet ./...) - Coverage ≥95% for new code
Roadmap
Phase 1 (Current)
- ✅ Project setup
- 🚧 Core date operations
- 🚧 Parsing and formatting
- 🚧 Timezone handling
- 🚧 i18n (EN, DE)
Phase 2
- Date ranges and series
- Batch operations
- Performance optimizations
Phase 3
- Holiday calendars
- Business day calculations
- Extended language support
Acknowledgments
Inspired by Moment.js, Carbon, and date-fns, but designed to be idiomatic Go.