Updated all references from code.beautifulmachines.dev/quando to code.beautifulmachines.dev/jakoubek/quando in: - Installation instructions - Import examples - Documentation link
155 lines
4.1 KiB
Markdown
155 lines
4.1 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
go get code.beautifulmachines.dev/jakoubek/quando
|
|
```
|
|
|
|
## Quick Start
|
|
|
|
```go
|
|
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:
|
|
|
|
```go
|
|
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":
|
|
|
|
```go
|
|
// 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:
|
|
|
|
```go
|
|
original := quando.Now()
|
|
modified := original.Add(1, quando.Days)
|
|
// original is unchanged
|
|
```
|
|
|
|
## Testing
|
|
|
|
quando provides a `Clock` interface for deterministic tests:
|
|
|
|
```go
|
|
// 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](https://pkg.go.dev/code.beautifulmachines.dev/jakoubek/quando)
|
|
|
|
## License
|
|
|
|
MIT License - see [LICENSE](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](https://momentjs.com/), [Carbon](https://carbon.nesbot.com/), and [date-fns](https://date-fns.org/), but designed to be idiomatic Go.
|