Implement Parse() function that automatically detects and parses common date formats without requiring explicit layout strings. This provides an intuitive API for parsing dates from various sources while maintaining type safety through proper error handling. Supported formats: - ISO format (YYYY-MM-DD): "2026-02-09" - ISO with slash (YYYY/MM/DD): "2026/02/09" - EU format (DD.MM.YYYY): "09.02.2026" - RFC2822/RFC1123: "Mon, 09 Feb 2026 00:00:00 +0000" Key features: - Detects and rejects ambiguous slash formats (e.g., "01/02/2026") - Returns clear, contextual errors for invalid or ambiguous inputs - Never panics - all errors via return values - Zero allocations for successful parses - Comprehensive test coverage (98%) Performance results: - ISO format: 105.5 ns/op (94x faster than 10µs target) - ISO slash: 118.3 ns/op - EU format: 117.4 ns/op - RFC2822: 257.8 ns/op Test coverage: - 42 unit tests covering valid formats, error cases, edge cases - 3 example tests demonstrating usage patterns - Benchmarks for all format types - Parse() function: 100% coverage Files added: - parse.go: Main implementation with Parse() and helper functions - parse_test.go: Comprehensive test suite with table-driven tests Files modified: - example_test.go: Added ExampleParse examples |
||
|---|---|---|
| .beads | ||
| .github/workflows | ||
| .gitattributes | ||
| .gitignore | ||
| AGENTS.md | ||
| arithmetic.go | ||
| arithmetic_test.go | ||
| CLAUDE.md | ||
| clock.go | ||
| clock_test.go | ||
| date.go | ||
| date_test.go | ||
| diff.go | ||
| diff_test.go | ||
| errors.go | ||
| errors_test.go | ||
| example_test.go | ||
| go.mod | ||
| LICENSE | ||
| parse.go | ||
| parse_test.go | ||
| 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/jakoubek/quando
Quick Start
import "code.beautifulmachines.dev/jakoubek/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/jakoubek/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.