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

9.4 KiB
Raw Blame History

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

  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

// 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

// 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:

  1. Godoc comment starting with function name
  2. Example test in example_test.go for key functions
  3. 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:

  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)