- 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
335 lines
9.4 KiB
Markdown
335 lines
9.4 KiB
Markdown
# 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)
|