feat(quando-b4r): implement Add and Sub arithmetic operations

- Add Add(value, unit) method for all 8 units
- Add Sub(value, unit) method (wraps Add with negative value)
- Implement month-end overflow logic for Months/Quarters/Years
- When adding months, if target day doesn't exist, snap to last day of month
- Handle all month-end combinations correctly
- Leap year support (Feb 29 edge cases)
- DST-safe calendar day arithmetic for Days unit
- Negative value support (Add(-1) == Sub(1))
- Method chaining support (fluent API)
- Immutability verified (original date never modified)
- Timezone preservation across operations
- Comprehensive table-driven tests for month-end edge cases
- All 30/31 day month combinations tested
- Leap year tests (Feb 29 -> Feb 28)
- Cross-year boundary tests
- Negative value tests
- Method chaining tests
- Performance benchmarks (all < 1µs)
- 98.8% test coverage (exceeds 95% requirement)
- Godoc with month-end overflow examples

Benchmark results:
- AddDays: ~42ns (< 1µs target) ✓
- AddMonths: ~191ns (< 1µs target) ✓
- AddYears: ~202ns (< 1µs target) ✓
- Method chaining: ~263ns for 3 ops ✓
- Zero allocations for all operations

All acceptance criteria met:
✓ Add() implemented for all 8 units
✓ Sub() implemented for all 8 units
✓ Month-end overflow logic correct
✓ Leap year handling (Feb 29 edge cases)
✓ DST handling (calendar days)
✓ Negative values supported
✓ Method chaining works
✓ Unit tests with 98.8% coverage
✓ Table-driven tests for month-end edge cases
✓ Benchmarks meet <1µs target
✓ Godoc comments with month-end examples
This commit is contained in:
Oliver Jakoubek 2026-02-11 17:43:03 +01:00
commit 7c7cb1a4d9
4 changed files with 602 additions and 2 deletions

View file

@ -312,3 +312,56 @@ func Example_errorTypes() {
// invalid timezone
// date overflow
}
// ExampleDate_Add demonstrates date arithmetic
func ExampleDate_Add() {
date := quando.From(time.Date(2026, 1, 15, 12, 0, 0, 0, time.UTC))
fmt.Println("Original:", date)
fmt.Println("+1 day:", date.Add(1, quando.Days))
fmt.Println("+1 month:", date.Add(1, quando.Months))
fmt.Println("+1 year:", date.Add(1, quando.Years))
// Output:
// Original: 2026-01-15 12:00:00
// +1 day: 2026-01-16 12:00:00
// +1 month: 2026-02-15 12:00:00
// +1 year: 2027-01-15 12:00:00
}
// ExampleDate_Add_monthEndOverflow demonstrates month-end overflow behavior
func ExampleDate_Add_monthEndOverflow() {
// When adding months, if target day doesn't exist, snap to month end
date := quando.From(time.Date(2026, 1, 31, 12, 0, 0, 0, time.UTC))
fmt.Println("Jan 31 + 1 month:", date.Add(1, quando.Months))
fmt.Println("Jan 31 + 2 months:", date.Add(2, quando.Months))
// Output:
// Jan 31 + 1 month: 2026-02-28 12:00:00
// Jan 31 + 2 months: 2026-03-31 12:00:00
}
// ExampleDate_Sub demonstrates date subtraction
func ExampleDate_Sub() {
date := quando.From(time.Date(2026, 3, 31, 12, 0, 0, 0, time.UTC))
fmt.Println("Original:", date)
fmt.Println("-1 day:", date.Sub(1, quando.Days))
fmt.Println("-1 month:", date.Sub(1, quando.Months))
// Output:
// Original: 2026-03-31 12:00:00
// -1 day: 2026-03-30 12:00:00
// -1 month: 2026-02-28 12:00:00
}
// ExampleDate_Add_chaining demonstrates method chaining
func ExampleDate_Add_chaining() {
date := quando.From(time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC))
result := date.
Add(1, quando.Months).
Add(15, quando.Days).
Sub(2, quando.Hours)
fmt.Println(result)
// Output: 2026-02-16 10:00:00
}