- Initialize go.mod with module path code.beautifulmachines.dev/quando - Set Go version to 1.22+ (using 1.25.6) - Create directory structure (internal/calc/, .github/workflows/) - Add comprehensive README.md with project overview, features, and examples - Add MIT LICENSE - Populate .gitignore for Go projects - Create GitHub Actions CI workflow for testing, linting, and benchmarking All acceptance criteria met: ✓ go.mod initialized with correct module path ✓ Go 1.22+ specified in go.mod ✓ Directory structure created ✓ README.md with project overview ✓ LICENSE file (MIT) ✓ .gitignore for Go projects ✓ Basic CI/CD workflow
9.4 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
quando is a standalone Go library for intuitive and idiomatic date calculations. It provides a fluent API for complex date operations that are cumbersome or impossible with the Go standard library alone.
Vision: The preferred Go library for date calculations – as natural and intuitive as Moment.js or Carbon, but Go-idiomatic and without external dependencies.
Target: Go 1.22+, zero dependencies (stdlib only), MIT License
Development Commands
# Run tests
go test ./... -v
# Run tests with coverage
go test ./... -cover -coverprofile=coverage.out
go tool cover -html=coverage.out
# Run a single test
go test -v -run TestFunctionName
# Run benchmarks
go test -bench=. -benchmem
# Run specific benchmark
go test -bench=BenchmarkAdd -benchmem
# Format code
go fmt ./...
# Lint (when golangci-lint is configured)
golangci-lint run
# Vet code
go vet ./...
# Build (library - no binary)
go build ./...
# Tidy dependencies
go mod tidy
Architecture Overview
Core Design Principles
- Fluent API: All operations return
quando.Datefor method chaining - Immutability: Every operation returns a new
Dateinstance (thread-safe by design) - Zero Dependencies: Only Go stdlib (except optional i18n extensions)
- Stdlib Delegation: Wrap
time.Time, don't reimplement it - No Panics: All errors via return values (except
Must*variants for tests)
Package Structure (Flat Layout)
quando/
├── quando.go # Package-level functions (Now, From, Parse, Diff)
├── date.go # Date type, core methods, conversions
├── arithmetic.go # Add, Sub (includes month-end overflow logic)
├── snap.go # StartOf, EndOf, Next, Prev
├── diff.go # Duration type, difference calculations
├── inspect.go # WeekNumber, Quarter, DayOfYear, etc.
├── format.go # Format, FormatLayout, preset constants
├── parse.go # Parse, ParseWithLayout, ParseRelative
├── clock.go # Clock interface for testability
├── i18n.go # Internationalization (EN, DE in Phase 1)
├── errors.go # Custom error types
└── internal/
└── calc/ # Non-exported helper functions
Rationale: Flat package structure keeps all functions under quando.* namespace, avoiding complex imports and cyclic dependencies.
Core Types
// Date wraps time.Time and provides fluent API
type Date struct {
t time.Time
lang Lang // optional, for formatting
}
// Unit represents time units for arithmetic
type Unit int
const (
Seconds Unit = iota
Minutes
Hours
Days
Weeks
Months
Quarters
Years
)
// Duration represents time differences
type Duration struct {
// Methods: Days(), Months(), Years(), Human(), etc.
}
// Clock interface for testability
type Clock interface {
Now() Date
From(t time.Time) Date
}
Critical Implementation Details
Month-End Overflow Behavior
When adding months, if the target date doesn't exist, snap to month end:
2026-01-31+ 1 month =2026-02-28(February end)2026-01-24+ 1 month =2026-02-24(regular)2026-05-31+ 1 month =2026-06-30(June has 30 days)
This is a must-have feature and requires extensive edge case testing.
DST Handling
Add(1, Days) means "same time on next calendar day", NOT "24 hours later":
- Example:
2026-03-31 02:00 CET+ 1 Day =2026-04-01 02:00 CEST(only 23 actual hours due to DST) - Rationale: Humans think in calendar days, not hour deltas
Week Start Convention
- Default: Monday (ISO 8601)
- StartOf(Week): Returns Monday 00:00:00
- EndOf(Week): Returns Sunday 23:59:59
- WeekNumber: ISO 8601 (Week 1 = first week containing Thursday)
Next/Prev Behavior
Next(Monday): Always NEXT Monday, never today (even if today is Monday)Prev(Friday): Always PREVIOUS Friday, never today
Parsing Ambiguity Rules
Slash formats without year prefix are ambiguous and must error:
2026-02-01✅ ISO, unambiguous01.02.2026✅ EU (dot separator indicates EU convention)2026/02/09✅ ISO with slash (year prefix is unambiguous)01/02/2026❌ ERROR (ambiguous: US vs EU)
Use ParseWithLayout() for explicit format handling.
Testing Requirements
Coverage Target
Minimum 95% for all calculation functions.
Critical Test Scenarios
- Month arithmetic: Overflow edge cases, leap years
- Snap operations: All units (Week, Month, Quarter, Year)
- Next/Prev: Same weekday edge case (must skip)
- Diff calculations: Cross year boundaries, leap years, negative diffs
- DST handling: Add operations across DST transitions
- Parsing: All formats, ambiguous inputs, invalid inputs
- WeekNumber: ISO 8601 compliance (Week 1 contains Thursday)
Test Organization
// Use table-driven tests for edge cases
func TestAddMonths(t *testing.T) {
tests := []struct {
name string
input string
months int
expected string
}{
{"month-end overflow", "2026-01-31", 1, "2026-02-28"},
{"regular addition", "2026-01-24", 1, "2026-02-24"},
// ... more cases
}
// ...
}
Testability Pattern
Use Clock interface for deterministic tests:
// Production code
date := quando.Now()
// Test code
clock := quando.NewFixedClock(time.Date(2026, 2, 9, 12, 0, 0, 0, time.UTC))
date := clock.Now()
Performance Goals
All benchmarks must meet these targets:
- Add/Sub operations: < 1 µs
- Diff calculations: < 2 µs (< 1 µs for integer variants)
- Formatting: < 10 µs (< 5 µs without i18n)
- Parsing: < 10 µs (automatic), < 20 µs (relative)
- Memory: Zero allocations for chained operations (except final result)
Run benchmarks with:
go test -bench=. -benchmem -benchtime=10s
Error Handling
Sentinel Errors
var (
ErrInvalidFormat = errors.New("invalid date format")
ErrInvalidTimezone = errors.New("invalid timezone")
ErrOverflow = errors.New("date overflow")
)
Error Wrapping
return Date{}, fmt.Errorf("parsing date: %w", err)
Panic Policy
NEVER panic except in Must* variants:
MustParse(s string) Date- for tests/initialization only- Document clearly that
Must*functions panic on error
Internationalization
Phase 1 Languages
- EN (English) - default
- DE (Deutsch) - must-have for Phase 1
i18n Applies To
Format(Long)- "February 9, 2026" vs "9. Februar 2026"- Custom layouts with month/weekday names
Human()- "10 months, 16 days" vs "10 Monate, 16 Tage"
i18n Does NOT Apply To
- ISO, EU, US, RFC2822 formats (always language-independent)
- Numeric outputs (WeekNumber, Quarter, etc.)
Code Style
Go Idioms
- Follow standard Go conventions (go fmt, go vet)
- Use godoc comments for all exported types/functions
- Prefer composition over inheritance
- Keep functions focused and single-purpose
Documentation
Every exported function needs:
- Godoc comment starting with function name
- Example test in
example_test.gofor key functions - Edge case documentation where applicable
// Add adds the specified number of units to the date.
// When adding months, if the target day doesn't exist, it snaps to month end.
//
// Example:
// quando.From(date).Add(2, quando.Months)
func (d Date) Add(value int, unit Unit) Date {
// ...
}
Beads Workflow
This project uses bd (beads) for issue tracking:
bd ready # Find available work
bd show <id> # View issue details
bd update <id> --status in_progress # Claim work
bd close <id> # Complete work
bd sync # Sync with git
See AGENTS.md for detailed workflow including the two-phase implement/finalize process.
Definition of Done
A feature is complete when:
- Implementation: Code follows Go idioms, all exports documented
- Tests: Min 95% coverage, edge cases covered, benchmarks meet goals
- Documentation: Godoc complete, README updated, example tests added
- CI/CD: All tests pass, linting clean (when CI is configured)
Common Pitfalls
- Month arithmetic: Don't forget month-end overflow logic - this is complex
- DST transitions: Test across DST boundaries in multiple timezones
- ISO 8601 week numbers: Week 1 is the first week with Thursday, not January 1st
- Immutability: Every method must return new Date, never modify receiver
- Time zones: Always handle invalid IANA names with errors, never panic
- Parsing ambiguity: Slash dates without year prefix must error
Development Priorities (Phase 1)
- Core infrastructure (Date type, conversions, Clock abstraction)
- Arithmetic operations (Add, Sub with month-end overflow)
- Snap operations (StartOf, EndOf, Next, Prev)
- Difference calculations (Diff, Duration, Human format)
- Parsing (automatic, explicit, relative)
- Formatting (presets, custom layouts, i18n)
- Date inspection (WeekNumber, Quarter, DayOfYear, etc.)
- Timezone support and DST handling
Total estimated timeline: 14 weeks (~3.5 months) per PRD.
Future Phases (Out of Scope for Phase 1)
- HTTP/API layer (separate web server)
- Holidays & business days (Phase 3)
- Date series/ranges (Phase 2)
- Batch operations (Phase 2)
- Additional languages beyond EN/DE (22 more languages planned)