quando/CLAUDE.md
Oliver Jakoubek f748b0e134 feat(quando-91w): initialize project structure and tooling
- 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
2026-02-11 16:28:14 +01:00

335 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
```bash
# 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
1. **Fluent API**: All operations return `quando.Date` for method chaining
2. **Immutability**: Every operation returns a new `Date` instance (thread-safe by design)
3. **Zero Dependencies**: Only Go stdlib (except optional i18n extensions)
4. **Stdlib Delegation**: Wrap `time.Time`, don't reimplement it
5. **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
```go
// 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, unambiguous
- `01.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
1. **Month arithmetic**: Overflow edge cases, leap years
2. **Snap operations**: All units (Week, Month, Quarter, Year)
3. **Next/Prev**: Same weekday edge case (must skip)
4. **Diff calculations**: Cross year boundaries, leap years, negative diffs
5. **DST handling**: Add operations across DST transitions
6. **Parsing**: All formats, ambiguous inputs, invalid inputs
7. **WeekNumber**: ISO 8601 compliance (Week 1 contains Thursday)
### Test Organization
```go
// 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:
```go
// 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:
```bash
go test -bench=. -benchmem -benchtime=10s
```
## Error Handling
### Sentinel Errors
```go
var (
ErrInvalidFormat = errors.New("invalid date format")
ErrInvalidTimezone = errors.New("invalid timezone")
ErrOverflow = errors.New("date overflow")
)
```
### Error Wrapping
```go
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:
1. Godoc comment starting with function name
2. Example test in `example_test.go` for key functions
3. Edge case documentation where applicable
```go
// 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:
```bash
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:
1. **Implementation**: Code follows Go idioms, all exports documented
2. **Tests**: Min 95% coverage, edge cases covered, benchmarks meet goals
3. **Documentation**: Godoc complete, README updated, example tests added
4. **CI/CD**: All tests pass, linting clean (when CI is configured)
## Common Pitfalls
1. **Month arithmetic**: Don't forget month-end overflow logic - this is complex
2. **DST transitions**: Test across DST boundaries in multiple timezones
3. **ISO 8601 week numbers**: Week 1 is the first week with Thursday, not January 1st
4. **Immutability**: Every method must return new Date, never modify receiver
5. **Time zones**: Always handle invalid IANA names with errors, never panic
6. **Parsing ambiguity**: Slash dates without year prefix must error
## Development Priorities (Phase 1)
1. Core infrastructure (Date type, conversions, Clock abstraction)
2. Arithmetic operations (Add, Sub with month-end overflow)
3. Snap operations (StartOf, EndOf, Next, Prev)
4. Difference calculations (Diff, Duration, Human format)
5. Parsing (automatic, explicit, relative)
6. Formatting (presets, custom layouts, i18n)
7. Date inspection (WeekNumber, Quarter, DayOfYear, etc.)
8. 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)