Intuitive and idiomatic date calculations for Go
go
Find a file
Oliver Jakoubek 065b767b54 feat(quando-gr5): implement automatic date parsing with format detection
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
2026-02-11 18:53:16 +01:00
.beads feat(quando-gr5): implement automatic date parsing with format detection 2026-02-11 18:53:16 +01:00
.github/workflows feat(quando-91w): initialize project structure and tooling 2026-02-11 16:28:14 +01:00
.gitattributes feat(quando-91w): initialize project structure and tooling 2026-02-11 16:28:14 +01:00
.gitignore feat(quando-91w): initialize project structure and tooling 2026-02-11 16:28:14 +01:00
AGENTS.md feat(quando-91w): initialize project structure and tooling 2026-02-11 16:28:14 +01:00
arithmetic.go feat(quando-b4r): implement Add and Sub arithmetic operations 2026-02-11 17:43:03 +01:00
arithmetic_test.go feat(quando-b4r): implement Add and Sub arithmetic operations 2026-02-11 17:43:03 +01:00
CLAUDE.md feat(quando-91w): initialize project structure and tooling 2026-02-11 16:28:14 +01:00
clock.go feat(quando-vih): implement Clock abstraction for testability 2026-02-11 16:33:23 +01:00
clock_test.go feat(quando-vih): implement Clock abstraction for testability 2026-02-11 16:33:23 +01:00
date.go feat(quando-j2s): implement core Date type and conversions 2026-02-11 16:31:21 +01:00
date_test.go feat(quando-j2s): implement core Date type and conversions 2026-02-11 16:31:21 +01:00
diff.go feat(quando-ljj): implement Duration type and Diff calculation 2026-02-11 17:38:22 +01:00
diff_test.go feat(quando-ljj): implement Duration type and Diff calculation 2026-02-11 17:38:22 +01:00
errors.go feat(quando-36t): implement error types and handling 2026-02-11 17:40:01 +01:00
errors_test.go feat(quando-36t): implement error types and handling 2026-02-11 17:40:01 +01:00
example_test.go feat(quando-gr5): implement automatic date parsing with format detection 2026-02-11 18:53:16 +01:00
go.mod chore: update module name to code.beautifulmachines.dev/jakoubek/quando 2026-02-11 17:25:25 +01:00
LICENSE feat(quando-91w): initialize project structure and tooling 2026-02-11 16:28:14 +01:00
parse.go feat(quando-gr5): implement automatic date parsing with format detection 2026-02-11 18:53:16 +01:00
parse_test.go feat(quando-gr5): implement automatic date parsing with format detection 2026-02-11 18:53:16 +01:00
PRD.md feat(quando-91w): initialize project structure and tooling 2026-02-11 16:28:14 +01:00
quando.go feat(quando-j2s): implement core Date type and conversions 2026-02-11 16:31:21 +01:00
README.md docs: update module path in README to include jakoubek namespace 2026-02-11 17:25:43 +01:00
snap.go feat(quando-9sf): implement Next and Prev weekday navigation 2026-02-11 17:33:54 +01:00
snap_test.go feat(quando-9sf): implement Next and Prev weekday navigation 2026-02-11 17:33:54 +01:00
unit.go feat(quando-4bh): implement Unit type and constants 2026-02-11 16:34:42 +01:00
unit_test.go feat(quando-4bh): implement Unit type and constants 2026-02-11 16:34:42 +01:00

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.