feat(quando-36t): implement error types and handling
- Create errors.go with sentinel errors - Define ErrInvalidFormat for parsing errors - Define ErrInvalidTimezone for timezone errors - Define ErrOverflow for date arithmetic overflow - Comprehensive godoc for each error with usage context - Document no-panic policy in error handling - Document Must* variant panic behavior - Example tests showing error handling patterns - Tests for error uniqueness and errors.Is compatibility - 100% coverage for error definitions Error Handling Policy: - Library never panics in normal operations - All errors returned as values - Use errors.Is() for error type checking - Must* functions panic (for test/init use only) All acceptance criteria met: ✓ errors.go file created ✓ ErrInvalidFormat defined ✓ ErrInvalidTimezone defined ✓ ErrOverflow defined ✓ Godoc for each error with usage context ✓ Documentation of no-panic policy ✓ Documentation of Must* panic behavior ✓ Example tests showing error handling patterns
This commit is contained in:
parent
e5ece8d480
commit
2bf1df03ea
4 changed files with 189 additions and 2 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
{"id":"quando-10t","title":"Human-readable duration format with i18n","description":"Implement Human() method on Duration for human-readable output with internationalization.\n\n**API:**\n```go\nfunc (dur Duration) Human() string\nfunc (dur Duration) Human(lang Lang) string\n```\n\n**Adaptive Granularity:**\nAlways show the two largest relevant units:\n\n| Difference | EN Output | DE Output |\n|------------|-----------|-----------|\n| 10 months, 16 days | \"10 months, 16 days\" | \"10 Monate, 16 Tage\" |\n| 2 days, 5 hours | \"2 days, 5 hours\" | \"2 Tage, 5 Stunden\" |\n| 3 hours, 20 minutes | \"3 hours, 20 minutes\" | \"3 Stunden, 20 Minuten\" |\n| 45 seconds | \"45 seconds\" | \"45 Sekunden\" |\n| 0 | \"0 seconds\" | \"0 Sekunden\" |\n\n**Language Support (Phase 1):**\n- EN (English) - default\n- DE (Deutsch) - must-have\n\n## Acceptance Criteria\n- [ ] Human() without argument returns English\n- [ ] Human(Lang) accepts language parameter\n- [ ] Adaptive granularity: two largest units\n- [ ] English translations complete\n- [ ] German translations complete\n- [ ] Zero duration handled (\"0 seconds\")\n- [ ] Singular/plural forms correct (1 day vs 2 days)\n- [ ] Unit tests for all granularity levels\n- [ ] Unit tests for both EN and DE\n- [ ] Benchmark meets \u003c10µs target with i18n\n- [ ] Godoc comments with examples in both languages","status":"open","priority":2,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-02-11T16:21:12.954367096+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-02-11T16:21:12.954367096+01:00","dependencies":[{"issue_id":"quando-10t","depends_on_id":"quando-ljj","type":"blocks","created_at":"2026-02-11T16:23:09.100489733+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"quando-10t","depends_on_id":"quando-zbr","type":"blocks","created_at":"2026-02-11T16:23:09.139531731+01:00","created_by":"Oliver Jakoubek"}]}
|
{"id":"quando-10t","title":"Human-readable duration format with i18n","description":"Implement Human() method on Duration for human-readable output with internationalization.\n\n**API:**\n```go\nfunc (dur Duration) Human() string\nfunc (dur Duration) Human(lang Lang) string\n```\n\n**Adaptive Granularity:**\nAlways show the two largest relevant units:\n\n| Difference | EN Output | DE Output |\n|------------|-----------|-----------|\n| 10 months, 16 days | \"10 months, 16 days\" | \"10 Monate, 16 Tage\" |\n| 2 days, 5 hours | \"2 days, 5 hours\" | \"2 Tage, 5 Stunden\" |\n| 3 hours, 20 minutes | \"3 hours, 20 minutes\" | \"3 Stunden, 20 Minuten\" |\n| 45 seconds | \"45 seconds\" | \"45 Sekunden\" |\n| 0 | \"0 seconds\" | \"0 Sekunden\" |\n\n**Language Support (Phase 1):**\n- EN (English) - default\n- DE (Deutsch) - must-have\n\n## Acceptance Criteria\n- [ ] Human() without argument returns English\n- [ ] Human(Lang) accepts language parameter\n- [ ] Adaptive granularity: two largest units\n- [ ] English translations complete\n- [ ] German translations complete\n- [ ] Zero duration handled (\"0 seconds\")\n- [ ] Singular/plural forms correct (1 day vs 2 days)\n- [ ] Unit tests for all granularity levels\n- [ ] Unit tests for both EN and DE\n- [ ] Benchmark meets \u003c10µs target with i18n\n- [ ] Godoc comments with examples in both languages","status":"open","priority":2,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-02-11T16:21:12.954367096+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-02-11T16:21:12.954367096+01:00","dependencies":[{"issue_id":"quando-10t","depends_on_id":"quando-ljj","type":"blocks","created_at":"2026-02-11T16:23:09.100489733+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"quando-10t","depends_on_id":"quando-zbr","type":"blocks","created_at":"2026-02-11T16:23:09.139531731+01:00","created_by":"Oliver Jakoubek"}]}
|
||||||
{"id":"quando-36t","title":"Error types and handling","description":"Define custom error types and establish error handling patterns for the library.\n\n**Sentinel Errors:**\n```go\nvar (\n ErrInvalidFormat = errors.New(\"invalid date format\")\n ErrInvalidTimezone = errors.New(\"invalid timezone\")\n ErrOverflow = errors.New(\"date overflow\")\n)\n```\n\n**Error Handling Principles:**\n1. NEVER panic (except Must* variants)\n2. Use sentinel errors for known error types\n3. Wrap errors with fmt.Errorf(\"%w\") for context\n4. Return clear, actionable error messages\n\n**Error Categories:**\n- Parse errors: Invalid formats, ambiguous inputs\n- Timezone errors: Unknown IANA names\n- Overflow errors: Date arithmetic outside Go's time.Time range\n\n**Documentation:**\n- Document that library never panics in normal operation\n- Document that Must* variants DO panic\n- Provide error handling examples\n\n## Acceptance Criteria\n- [ ] errors.go file created\n- [ ] ErrInvalidFormat defined\n- [ ] ErrInvalidTimezone defined\n- [ ] ErrOverflow defined\n- [ ] Godoc for each error with usage context\n- [ ] Documentation of no-panic policy\n- [ ] Documentation of Must* panic behavior\n- [ ] Example tests showing error handling patterns","status":"open","priority":1,"issue_type":"task","owner":"mail@oliverjakoubek.de","created_at":"2026-02-11T16:22:22.314746489+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-02-11T16:22:22.314746489+01:00","dependencies":[{"issue_id":"quando-36t","depends_on_id":"quando-91w","type":"blocks","created_at":"2026-02-11T16:23:05.380454674+01:00","created_by":"Oliver Jakoubek"}]}
|
{"id":"quando-36t","title":"Error types and handling","description":"Define custom error types and establish error handling patterns for the library.\n\n**Sentinel Errors:**\n```go\nvar (\n ErrInvalidFormat = errors.New(\"invalid date format\")\n ErrInvalidTimezone = errors.New(\"invalid timezone\")\n ErrOverflow = errors.New(\"date overflow\")\n)\n```\n\n**Error Handling Principles:**\n1. NEVER panic (except Must* variants)\n2. Use sentinel errors for known error types\n3. Wrap errors with fmt.Errorf(\"%w\") for context\n4. Return clear, actionable error messages\n\n**Error Categories:**\n- Parse errors: Invalid formats, ambiguous inputs\n- Timezone errors: Unknown IANA names\n- Overflow errors: Date arithmetic outside Go's time.Time range\n\n**Documentation:**\n- Document that library never panics in normal operation\n- Document that Must* variants DO panic\n- Provide error handling examples\n\n## Acceptance Criteria\n- [ ] errors.go file created\n- [ ] ErrInvalidFormat defined\n- [ ] ErrInvalidTimezone defined\n- [ ] ErrOverflow defined\n- [ ] Godoc for each error with usage context\n- [ ] Documentation of no-panic policy\n- [ ] Documentation of Must* panic behavior\n- [ ] Example tests showing error handling patterns","status":"in_progress","priority":1,"issue_type":"task","owner":"mail@oliverjakoubek.de","created_at":"2026-02-11T16:22:22.314746489+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-02-11T17:38:40.5628418+01:00","dependencies":[{"issue_id":"quando-36t","depends_on_id":"quando-91w","type":"blocks","created_at":"2026-02-11T16:23:05.380454674+01:00","created_by":"Oliver Jakoubek"}],"comments":[{"id":8,"issue_id":"quando-36t","author":"Oliver Jakoubek","text":"Plan: 1) Create errors.go with sentinel errors (ErrInvalidFormat, ErrInvalidTimezone, ErrOverflow), 2) Add comprehensive godoc comments for each error with usage context, 3) Document no-panic policy in package documentation, 4) Document Must* variant panic behavior, 5) Add example tests showing error handling patterns (error checking, unwrapping)","created_at":"2026-02-11T16:38:47Z"}]}
|
||||||
{"id":"quando-41g","title":"Timezone support and conversion","description":"Implement timezone conversion with proper DST handling.\n\n**API:**\n```go\nfunc (d Date) In(location string) (Date, error)\n```\n\n**Behavior:**\n- Convert date to specified IANA timezone\n- Return error for invalid timezone names (never panic)\n- Use IANA Timezone Database\n- Default timezone: UTC if not specified\n\n**DST Handling:**\nCritical: `Add(1, Days)` means \"same time next calendar day\", NOT 24 hours\n- Example: 2026-03-31 02:00 CET + 1 Day = 2026-04-01 02:00 CEST\n- This is only 23 actual hours due to DST transition\n- Rationale: Humans think in calendar days, not hour deltas\n\n**Error Handling:**\n- Validate IANA timezone names\n- Return clear error for unknown timezones\n- Return clear error for empty timezone string\n\n## Acceptance Criteria\n- [ ] In(location) implemented\n- [ ] Uses IANA Timezone Database\n- [ ] Converts to specified timezone correctly\n- [ ] Invalid timezone names return error\n- [ ] Empty string returns error\n- [ ] Never panics on invalid input\n- [ ] DST handling: Add(Days) preserves wall clock time\n- [ ] Tests across DST transitions (spring and fall)\n- [ ] Tests for multiple timezones (Europe/Berlin, America/New_York, etc.)\n- [ ] Unit tests with 95%+ coverage\n- [ ] Godoc with DST behavior clearly explained\n- [ ] Example showing DST-safe arithmetic","status":"open","priority":1,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-02-11T16:22:14.704688038+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-02-11T16:22:14.704688038+01:00","dependencies":[{"issue_id":"quando-41g","depends_on_id":"quando-j2s","type":"blocks","created_at":"2026-02-11T16:23:13.346573362+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"quando-41g","depends_on_id":"quando-36t","type":"blocks","created_at":"2026-02-11T16:23:13.384247178+01:00","created_by":"Oliver Jakoubek"}]}
|
{"id":"quando-41g","title":"Timezone support and conversion","description":"Implement timezone conversion with proper DST handling.\n\n**API:**\n```go\nfunc (d Date) In(location string) (Date, error)\n```\n\n**Behavior:**\n- Convert date to specified IANA timezone\n- Return error for invalid timezone names (never panic)\n- Use IANA Timezone Database\n- Default timezone: UTC if not specified\n\n**DST Handling:**\nCritical: `Add(1, Days)` means \"same time next calendar day\", NOT 24 hours\n- Example: 2026-03-31 02:00 CET + 1 Day = 2026-04-01 02:00 CEST\n- This is only 23 actual hours due to DST transition\n- Rationale: Humans think in calendar days, not hour deltas\n\n**Error Handling:**\n- Validate IANA timezone names\n- Return clear error for unknown timezones\n- Return clear error for empty timezone string\n\n## Acceptance Criteria\n- [ ] In(location) implemented\n- [ ] Uses IANA Timezone Database\n- [ ] Converts to specified timezone correctly\n- [ ] Invalid timezone names return error\n- [ ] Empty string returns error\n- [ ] Never panics on invalid input\n- [ ] DST handling: Add(Days) preserves wall clock time\n- [ ] Tests across DST transitions (spring and fall)\n- [ ] Tests for multiple timezones (Europe/Berlin, America/New_York, etc.)\n- [ ] Unit tests with 95%+ coverage\n- [ ] Godoc with DST behavior clearly explained\n- [ ] Example showing DST-safe arithmetic","status":"open","priority":1,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-02-11T16:22:14.704688038+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-02-11T16:22:14.704688038+01:00","dependencies":[{"issue_id":"quando-41g","depends_on_id":"quando-j2s","type":"blocks","created_at":"2026-02-11T16:23:13.346573362+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"quando-41g","depends_on_id":"quando-36t","type":"blocks","created_at":"2026-02-11T16:23:13.384247178+01:00","created_by":"Oliver Jakoubek"}]}
|
||||||
{"id":"quando-4bh","title":"Unit type and constants","description":"Define Unit type for time unit constants used in arithmetic operations.\n\n**Technical Details:**\n```go\ntype Unit int\n\nconst (\n Seconds Unit = iota\n Minutes\n Hours\n Days\n Weeks\n Months\n Quarters\n Years\n)\n```\n\n**Design:**\n- Type-safe constants (compile-time safety)\n- Use iota for clear ordering\n- Optional internal ParseUnit(string) for external inputs\n\n## Acceptance Criteria\n- [ ] Unit type defined as int\n- [ ] All 8 unit constants defined (Seconds through Years)\n- [ ] Units use iota for ordering\n- [ ] Godoc comments for Unit type and constants\n- [ ] Unit tests verifying constants","status":"closed","priority":0,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-02-11T16:20:37.246514285+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-02-11T16:34:46.001417204+01:00","closed_at":"2026-02-11T16:34:46.001417204+01:00","close_reason":"Closed","dependencies":[{"issue_id":"quando-4bh","depends_on_id":"quando-91w","type":"blocks","created_at":"2026-02-11T16:23:05.349519431+01:00","created_by":"Oliver Jakoubek"}],"comments":[{"id":4,"issue_id":"quando-4bh","author":"Oliver Jakoubek","text":"Plan: 1) Create unit.go with Unit type as int, 2) Define all 8 unit constants (Seconds, Minutes, Hours, Days, Weeks, Months, Quarters, Years) using iota, 3) Add String() method for better debugging, 4) Add comprehensive godoc comments, 5) Write unit tests verifying constants and ordering, 6) Add example tests","created_at":"2026-02-11T15:33:41Z"}]}
|
{"id":"quando-4bh","title":"Unit type and constants","description":"Define Unit type for time unit constants used in arithmetic operations.\n\n**Technical Details:**\n```go\ntype Unit int\n\nconst (\n Seconds Unit = iota\n Minutes\n Hours\n Days\n Weeks\n Months\n Quarters\n Years\n)\n```\n\n**Design:**\n- Type-safe constants (compile-time safety)\n- Use iota for clear ordering\n- Optional internal ParseUnit(string) for external inputs\n\n## Acceptance Criteria\n- [ ] Unit type defined as int\n- [ ] All 8 unit constants defined (Seconds through Years)\n- [ ] Units use iota for ordering\n- [ ] Godoc comments for Unit type and constants\n- [ ] Unit tests verifying constants","status":"closed","priority":0,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-02-11T16:20:37.246514285+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-02-11T16:34:46.001417204+01:00","closed_at":"2026-02-11T16:34:46.001417204+01:00","close_reason":"Closed","dependencies":[{"issue_id":"quando-4bh","depends_on_id":"quando-91w","type":"blocks","created_at":"2026-02-11T16:23:05.349519431+01:00","created_by":"Oliver Jakoubek"}],"comments":[{"id":4,"issue_id":"quando-4bh","author":"Oliver Jakoubek","text":"Plan: 1) Create unit.go with Unit type as int, 2) Define all 8 unit constants (Seconds, Minutes, Hours, Days, Weeks, Months, Quarters, Years) using iota, 3) Add String() method for better debugging, 4) Add comprehensive godoc comments, 5) Write unit tests verifying constants and ordering, 6) Add example tests","created_at":"2026-02-11T15:33:41Z"}]}
|
||||||
{"id":"quando-5ib","title":"Date inspection methods","description":"Implement date inspection methods for querying metadata about a date.\n\n**Individual Methods:**\n```go\nfunc (d Date) WeekNumber() int\nfunc (d Date) Quarter() int\nfunc (d Date) DayOfYear() int\nfunc (d Date) IsWeekend() bool\nfunc (d Date) IsLeapYear() bool\n```\n\n**Aggregated Method:**\n```go\ntype DateInfo struct {\n WeekNumber int\n Quarter int\n DayOfYear int\n IsWeekend bool\n IsLeapYear bool\n Unix int64\n}\n\nfunc (d Date) Info() DateInfo\n```\n\n**Specifications:**\n- **WeekNumber**: ISO 8601 (Monday first, Week 1 = first week containing Thursday)\n- **Quarter**: 1-4 (Q1=Jan-Mar, Q2=Apr-Jun, Q3=Jul-Sep, Q4=Oct-Dec)\n- **DayOfYear**: 1-366 (considering leap years)\n- **IsWeekend**: Saturday or Sunday (not configurable in Phase 1)\n- **IsLeapYear**: Divisible by 4, except centuries, except divisible by 400\n\n## Acceptance Criteria\n- [ ] WeekNumber() returns ISO 8601 week number\n- [ ] Quarter() returns 1-4\n- [ ] DayOfYear() returns 1-366\n- [ ] IsWeekend() returns true for Sat/Sun\n- [ ] IsLeapYear() returns true for leap years\n- [ ] Info() returns struct with all fields\n- [ ] ISO 8601 week compliance (Week 1 contains Thursday)\n- [ ] Leap year rules correct (4, 100, 400 rule)\n- [ ] Unit tests for all methods\n- [ ] Edge cases: year boundaries, week 53, leap years\n- [ ] Benchmarks meet \u003c1µs target\n- [ ] Godoc for each method with conventions documented","status":"open","priority":2,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-02-11T16:22:06.966079923+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-02-11T16:22:06.966079923+01:00","dependencies":[{"issue_id":"quando-5ib","depends_on_id":"quando-j2s","type":"blocks","created_at":"2026-02-11T16:23:13.310800388+01:00","created_by":"Oliver Jakoubek"}]}
|
{"id":"quando-5ib","title":"Date inspection methods","description":"Implement date inspection methods for querying metadata about a date.\n\n**Individual Methods:**\n```go\nfunc (d Date) WeekNumber() int\nfunc (d Date) Quarter() int\nfunc (d Date) DayOfYear() int\nfunc (d Date) IsWeekend() bool\nfunc (d Date) IsLeapYear() bool\n```\n\n**Aggregated Method:**\n```go\ntype DateInfo struct {\n WeekNumber int\n Quarter int\n DayOfYear int\n IsWeekend bool\n IsLeapYear bool\n Unix int64\n}\n\nfunc (d Date) Info() DateInfo\n```\n\n**Specifications:**\n- **WeekNumber**: ISO 8601 (Monday first, Week 1 = first week containing Thursday)\n- **Quarter**: 1-4 (Q1=Jan-Mar, Q2=Apr-Jun, Q3=Jul-Sep, Q4=Oct-Dec)\n- **DayOfYear**: 1-366 (considering leap years)\n- **IsWeekend**: Saturday or Sunday (not configurable in Phase 1)\n- **IsLeapYear**: Divisible by 4, except centuries, except divisible by 400\n\n## Acceptance Criteria\n- [ ] WeekNumber() returns ISO 8601 week number\n- [ ] Quarter() returns 1-4\n- [ ] DayOfYear() returns 1-366\n- [ ] IsWeekend() returns true for Sat/Sun\n- [ ] IsLeapYear() returns true for leap years\n- [ ] Info() returns struct with all fields\n- [ ] ISO 8601 week compliance (Week 1 contains Thursday)\n- [ ] Leap year rules correct (4, 100, 400 rule)\n- [ ] Unit tests for all methods\n- [ ] Edge cases: year boundaries, week 53, leap years\n- [ ] Benchmarks meet \u003c1µs target\n- [ ] Godoc for each method with conventions documented","status":"open","priority":2,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-02-11T16:22:06.966079923+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-02-11T16:22:06.966079923+01:00","dependencies":[{"issue_id":"quando-5ib","depends_on_id":"quando-j2s","type":"blocks","created_at":"2026-02-11T16:23:13.310800388+01:00","created_by":"Oliver Jakoubek"}]}
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
{"id":"quando-dsx","title":"Snap operations: StartOf and EndOf","description":"Implement StartOf and EndOf methods to jump to beginning/end of time units.\n\n**API:**\n```go\nfunc (d Date) StartOf(unit Unit) Date\nfunc (d Date) EndOf(unit Unit) Date\n```\n\n**Supported Units:** Week, Month, Quarter, Year\n\n**Behavior:**\n- **StartOf(Week)**: Monday 00:00:00 (ISO 8601 default)\n- **EndOf(Week)**: Sunday 23:59:59\n- **StartOf(Month)**: 1st day of month, 00:00:00\n- **EndOf(Month)**: Last day of month, 23:59:59\n- **StartOf(Quarter)**: Q1=Jan 1, Q2=Apr 1, Q3=Jul 1, Q4=Oct 1\n- **EndOf(Quarter)**: Q1=Mar 31, Q2=Jun 30, Q3=Sep 30, Q4=Dec 31\n- **StartOf(Year)**: Jan 1, 00:00:00\n- **EndOf(Year)**: Dec 31, 23:59:59\n\n**Quarter Definition:**\n- Q1 = January–March\n- Q2 = April–June\n- Q3 = July–September\n- Q4 = October–Dezember\n\n## Acceptance Criteria\n- [ ] StartOf(Week) returns Monday 00:00:00\n- [ ] EndOf(Week) returns Sunday 23:59:59\n- [ ] StartOf(Month) returns 1st day 00:00:00\n- [ ] EndOf(Month) handles all month lengths correctly\n- [ ] StartOf(Quarter) returns correct quarter start\n- [ ] EndOf(Quarter) returns correct quarter end (handles 30/31 day months)\n- [ ] StartOf(Year) returns Jan 1 00:00:00\n- [ ] EndOf(Year) returns Dec 31 23:59:59\n- [ ] Leap year handling for February\n- [ ] Unit tests for all units and edge cases\n- [ ] ISO 8601 week compliance tests\n- [ ] Benchmarks meet \u003c1µs target\n- [ ] Godoc comments with examples","status":"closed","priority":1,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-02-11T16:20:52.371452631+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-02-11T16:39:08.822973772+01:00","closed_at":"2026-02-11T16:39:08.822973772+01:00","close_reason":"Closed","dependencies":[{"issue_id":"quando-dsx","depends_on_id":"quando-j2s","type":"blocks","created_at":"2026-02-11T16:23:07.280217562+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"quando-dsx","depends_on_id":"quando-4bh","type":"blocks","created_at":"2026-02-11T16:23:07.316281123+01:00","created_by":"Oliver Jakoubek"}],"comments":[{"id":5,"issue_id":"quando-dsx","author":"Oliver Jakoubek","text":"Plan: 1) Create snap.go with StartOf() and EndOf() methods, 2) Implement Week snapping (Monday start, Sunday end per ISO 8601), 3) Implement Month snapping (handle all month lengths), 4) Implement Quarter snapping (Q1=Jan-Mar, Q2=Apr-Jun, Q3=Jul-Sep, Q4=Oct-Dec), 5) Implement Year snapping, 6) Add comprehensive unit tests (all units, edge cases, leap years, month-end variations), 7) Add benchmarks to meet \u003c1µs target, 8) Add godoc comments and example tests","created_at":"2026-02-11T15:35:05Z"}]}
|
{"id":"quando-dsx","title":"Snap operations: StartOf and EndOf","description":"Implement StartOf and EndOf methods to jump to beginning/end of time units.\n\n**API:**\n```go\nfunc (d Date) StartOf(unit Unit) Date\nfunc (d Date) EndOf(unit Unit) Date\n```\n\n**Supported Units:** Week, Month, Quarter, Year\n\n**Behavior:**\n- **StartOf(Week)**: Monday 00:00:00 (ISO 8601 default)\n- **EndOf(Week)**: Sunday 23:59:59\n- **StartOf(Month)**: 1st day of month, 00:00:00\n- **EndOf(Month)**: Last day of month, 23:59:59\n- **StartOf(Quarter)**: Q1=Jan 1, Q2=Apr 1, Q3=Jul 1, Q4=Oct 1\n- **EndOf(Quarter)**: Q1=Mar 31, Q2=Jun 30, Q3=Sep 30, Q4=Dec 31\n- **StartOf(Year)**: Jan 1, 00:00:00\n- **EndOf(Year)**: Dec 31, 23:59:59\n\n**Quarter Definition:**\n- Q1 = January–March\n- Q2 = April–June\n- Q3 = July–September\n- Q4 = October–Dezember\n\n## Acceptance Criteria\n- [ ] StartOf(Week) returns Monday 00:00:00\n- [ ] EndOf(Week) returns Sunday 23:59:59\n- [ ] StartOf(Month) returns 1st day 00:00:00\n- [ ] EndOf(Month) handles all month lengths correctly\n- [ ] StartOf(Quarter) returns correct quarter start\n- [ ] EndOf(Quarter) returns correct quarter end (handles 30/31 day months)\n- [ ] StartOf(Year) returns Jan 1 00:00:00\n- [ ] EndOf(Year) returns Dec 31 23:59:59\n- [ ] Leap year handling for February\n- [ ] Unit tests for all units and edge cases\n- [ ] ISO 8601 week compliance tests\n- [ ] Benchmarks meet \u003c1µs target\n- [ ] Godoc comments with examples","status":"closed","priority":1,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-02-11T16:20:52.371452631+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-02-11T16:39:08.822973772+01:00","closed_at":"2026-02-11T16:39:08.822973772+01:00","close_reason":"Closed","dependencies":[{"issue_id":"quando-dsx","depends_on_id":"quando-j2s","type":"blocks","created_at":"2026-02-11T16:23:07.280217562+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"quando-dsx","depends_on_id":"quando-4bh","type":"blocks","created_at":"2026-02-11T16:23:07.316281123+01:00","created_by":"Oliver Jakoubek"}],"comments":[{"id":5,"issue_id":"quando-dsx","author":"Oliver Jakoubek","text":"Plan: 1) Create snap.go with StartOf() and EndOf() methods, 2) Implement Week snapping (Monday start, Sunday end per ISO 8601), 3) Implement Month snapping (handle all month lengths), 4) Implement Quarter snapping (Q1=Jan-Mar, Q2=Apr-Jun, Q3=Jul-Sep, Q4=Oct-Dec), 5) Implement Year snapping, 6) Add comprehensive unit tests (all units, edge cases, leap years, month-end variations), 7) Add benchmarks to meet \u003c1µs target, 8) Add godoc comments and example tests","created_at":"2026-02-11T15:35:05Z"}]}
|
||||||
{"id":"quando-gr5","title":"Automatic date parsing","description":"Implement automatic parsing that detects common date formats without explicit layout.\n\n**API:**\n```go\nfunc Parse(s string) (Date, error)\n```\n\n**Supported Formats:**\n- ISO: \"2026-02-09\"\n- ISO with slash: \"2026/02/09\"\n- EU (dot separator): \"09.02.2026\"\n- RFC2822: \"Mon, 09 Feb 2026 00:00:00 +0000\"\n\n**Ambiguity Rules:**\nSlash formats without year prefix are AMBIGUOUS and must error:\n\n| Input | Recognition | Reason |\n|-------|-------------|--------|\n| 2026-02-01 | ✅ ISO | Standard format |\n| 01.02.2026 | ✅ EU | Dot = EU convention |\n| 2026/02/09 | ✅ ISO | Year prefix unambiguous |\n| 01/02/2026 | ❌ ERROR | Ambiguous (US vs EU) |\n\n**Error Handling:**\n- Return clear error for ambiguous formats\n- Return clear error for invalid dates\n- Never panic\n\n## Acceptance Criteria\n- [ ] Parse() implemented\n- [ ] ISO format recognized (YYYY-MM-DD)\n- [ ] ISO slash format recognized (YYYY/MM/DD)\n- [ ] EU format recognized (DD.MM.YYYY)\n- [ ] RFC2822 format recognized\n- [ ] Ambiguous slash formats return error\n- [ ] Invalid dates return error\n- [ ] Never panics on any input\n- [ ] Unit tests for all supported formats\n- [ ] Unit tests for ambiguous/invalid inputs\n- [ ] Benchmark meets \u003c10µs target\n- [ ] Godoc with ambiguity rules documented\n- [ ] Example tests showing supported formats","status":"open","priority":1,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-02-11T16:21:28.074836359+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-02-11T16:21:28.074836359+01:00","dependencies":[{"issue_id":"quando-gr5","depends_on_id":"quando-j2s","type":"blocks","created_at":"2026-02-11T16:23:11.106618119+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"quando-gr5","depends_on_id":"quando-36t","type":"blocks","created_at":"2026-02-11T16:23:11.142801721+01:00","created_by":"Oliver Jakoubek"}]}
|
{"id":"quando-gr5","title":"Automatic date parsing","description":"Implement automatic parsing that detects common date formats without explicit layout.\n\n**API:**\n```go\nfunc Parse(s string) (Date, error)\n```\n\n**Supported Formats:**\n- ISO: \"2026-02-09\"\n- ISO with slash: \"2026/02/09\"\n- EU (dot separator): \"09.02.2026\"\n- RFC2822: \"Mon, 09 Feb 2026 00:00:00 +0000\"\n\n**Ambiguity Rules:**\nSlash formats without year prefix are AMBIGUOUS and must error:\n\n| Input | Recognition | Reason |\n|-------|-------------|--------|\n| 2026-02-01 | ✅ ISO | Standard format |\n| 01.02.2026 | ✅ EU | Dot = EU convention |\n| 2026/02/09 | ✅ ISO | Year prefix unambiguous |\n| 01/02/2026 | ❌ ERROR | Ambiguous (US vs EU) |\n\n**Error Handling:**\n- Return clear error for ambiguous formats\n- Return clear error for invalid dates\n- Never panic\n\n## Acceptance Criteria\n- [ ] Parse() implemented\n- [ ] ISO format recognized (YYYY-MM-DD)\n- [ ] ISO slash format recognized (YYYY/MM/DD)\n- [ ] EU format recognized (DD.MM.YYYY)\n- [ ] RFC2822 format recognized\n- [ ] Ambiguous slash formats return error\n- [ ] Invalid dates return error\n- [ ] Never panics on any input\n- [ ] Unit tests for all supported formats\n- [ ] Unit tests for ambiguous/invalid inputs\n- [ ] Benchmark meets \u003c10µs target\n- [ ] Godoc with ambiguity rules documented\n- [ ] Example tests showing supported formats","status":"open","priority":1,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-02-11T16:21:28.074836359+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-02-11T16:21:28.074836359+01:00","dependencies":[{"issue_id":"quando-gr5","depends_on_id":"quando-j2s","type":"blocks","created_at":"2026-02-11T16:23:11.106618119+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"quando-gr5","depends_on_id":"quando-36t","type":"blocks","created_at":"2026-02-11T16:23:11.142801721+01:00","created_by":"Oliver Jakoubek"}]}
|
||||||
{"id":"quando-j2s","title":"Core infrastructure: Date type and conversions","description":"Implement the core Date type that wraps time.Time and provides the foundation for the fluent API.\n\n**Technical Details:**\n- `Date` struct with private `time.Time` field and optional `Lang` field\n- Package-level constructors: `Now()`, `From(time.Time)`\n- Conversion methods: `Time()` returns underlying time.Time\n- Unix timestamp support: `Unix()` and `FromUnix(int64)`\n\n**Implementation Notes:**\n- Date must wrap time.Time, not reimplement it\n- All operations return new Date instances (immutability)\n- Support full Go time.Time range (year 0-9999+)\n- Support negative Unix timestamps (before 1970)\n\n## Acceptance Criteria\n- [ ] Date struct defined with time.Time and Lang fields\n- [ ] Now() returns current date\n- [ ] From(time.Time) converts to Date\n- [ ] Time() extracts underlying time.Time\n- [ ] Unix() returns Unix timestamp (int64)\n- [ ] FromUnix(int64) creates Date from timestamp\n- [ ] Unit tests with 95%+ coverage\n- [ ] Godoc comments for all exported types/functions\n- [ ] Example tests in example_test.go","status":"closed","priority":0,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-02-11T16:20:29.134906992+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-02-11T16:31:25.561098309+01:00","closed_at":"2026-02-11T16:31:25.561098309+01:00","close_reason":"Closed","dependencies":[{"issue_id":"quando-j2s","depends_on_id":"quando-91w","type":"blocks","created_at":"2026-02-11T16:23:05.272420642+01:00","created_by":"Oliver Jakoubek"}],"comments":[{"id":2,"issue_id":"quando-j2s","author":"Oliver Jakoubek","text":"Plan: 1) Create date.go with Date struct (t time.Time, lang Lang), 2) Implement package-level constructors Now() and From(time.Time), 3) Add conversion methods Time(), Unix(), FromUnix(), 4) Create quando.go for package-level exports, 5) Write comprehensive unit tests covering edge cases (negative Unix timestamps, full time.Time range), 6) Add example tests in example_test.go, 7) Ensure all exports have godoc comments","created_at":"2026-02-11T15:28:39Z"}]}
|
{"id":"quando-j2s","title":"Core infrastructure: Date type and conversions","description":"Implement the core Date type that wraps time.Time and provides the foundation for the fluent API.\n\n**Technical Details:**\n- `Date` struct with private `time.Time` field and optional `Lang` field\n- Package-level constructors: `Now()`, `From(time.Time)`\n- Conversion methods: `Time()` returns underlying time.Time\n- Unix timestamp support: `Unix()` and `FromUnix(int64)`\n\n**Implementation Notes:**\n- Date must wrap time.Time, not reimplement it\n- All operations return new Date instances (immutability)\n- Support full Go time.Time range (year 0-9999+)\n- Support negative Unix timestamps (before 1970)\n\n## Acceptance Criteria\n- [ ] Date struct defined with time.Time and Lang fields\n- [ ] Now() returns current date\n- [ ] From(time.Time) converts to Date\n- [ ] Time() extracts underlying time.Time\n- [ ] Unix() returns Unix timestamp (int64)\n- [ ] FromUnix(int64) creates Date from timestamp\n- [ ] Unit tests with 95%+ coverage\n- [ ] Godoc comments for all exported types/functions\n- [ ] Example tests in example_test.go","status":"closed","priority":0,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-02-11T16:20:29.134906992+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-02-11T16:31:25.561098309+01:00","closed_at":"2026-02-11T16:31:25.561098309+01:00","close_reason":"Closed","dependencies":[{"issue_id":"quando-j2s","depends_on_id":"quando-91w","type":"blocks","created_at":"2026-02-11T16:23:05.272420642+01:00","created_by":"Oliver Jakoubek"}],"comments":[{"id":2,"issue_id":"quando-j2s","author":"Oliver Jakoubek","text":"Plan: 1) Create date.go with Date struct (t time.Time, lang Lang), 2) Implement package-level constructors Now() and From(time.Time), 3) Add conversion methods Time(), Unix(), FromUnix(), 4) Create quando.go for package-level exports, 5) Write comprehensive unit tests covering edge cases (negative Unix timestamps, full time.Time range), 6) Add example tests in example_test.go, 7) Ensure all exports have godoc comments","created_at":"2026-02-11T15:28:39Z"}]}
|
||||||
{"id":"quando-ljj","title":"Duration type and Diff calculation","description":"Implement Duration type and Diff function for calculating differences between dates.\n\n**API:**\n```go\nfunc Diff(a, b time.Time) Duration\n\ntype Duration struct {\n // private fields\n}\n\n// Integer methods (rounded down)\nfunc (dur Duration) Seconds() int64\nfunc (dur Duration) Minutes() int64\nfunc (dur Duration) Hours() int64\nfunc (dur Duration) Days() int\nfunc (dur Duration) Weeks() int\nfunc (dur Duration) Months() int\nfunc (dur Duration) Years() int\n\n// Float methods (precise)\nfunc (dur Duration) MonthsFloat() float64\nfunc (dur Duration) YearsFloat() float64\n```\n\n**Precision:**\n- Integer variants return rounded-down values\n- Float variants for precise calculations\n- Handle negative differences (date1 \u003c date2)\n- Cross year boundaries correctly\n- Handle leap years correctly\n\n## Acceptance Criteria\n- [ ] Duration type defined\n- [ ] Diff(a, b) returns Duration\n- [ ] All integer methods implemented (Seconds through Years)\n- [ ] Float methods for Months and Years implemented\n- [ ] Negative differences handled correctly\n- [ ] Calculations correct across year boundaries\n- [ ] Leap year handling correct\n- [ ] Unit tests with 95%+ coverage\n- [ ] Table-driven tests for various date ranges\n- [ ] Benchmarks meet \u003c1µs (int) and \u003c2µs (float) targets\n- [ ] Godoc comments with precision explanation","status":"in_progress","priority":1,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-02-11T16:21:06.159742785+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-02-11T17:34:11.698319053+01:00","dependencies":[{"issue_id":"quando-ljj","depends_on_id":"quando-j2s","type":"blocks","created_at":"2026-02-11T16:23:08.127266864+01:00","created_by":"Oliver Jakoubek"}],"comments":[{"id":7,"issue_id":"quando-ljj","author":"Oliver Jakoubek","text":"Plan: 1) Create diff.go with Duration type (private fields: start, end time.Time), 2) Implement Diff(a, b) package function, 3) Implement integer methods (Seconds through Years) with rounded-down values, 4) Implement float methods (MonthsFloat, YearsFloat) for precise calculations, 5) Handle negative differences (when a \u003c b), 6) Correctly handle year boundaries and leap years in month/year calculations, 7) Write comprehensive table-driven tests covering edge cases, 8) Add benchmarks, 9) Godoc comments with precision explanation","created_at":"2026-02-11T16:34:17Z"}]}
|
{"id":"quando-ljj","title":"Duration type and Diff calculation","description":"Implement Duration type and Diff function for calculating differences between dates.\n\n**API:**\n```go\nfunc Diff(a, b time.Time) Duration\n\ntype Duration struct {\n // private fields\n}\n\n// Integer methods (rounded down)\nfunc (dur Duration) Seconds() int64\nfunc (dur Duration) Minutes() int64\nfunc (dur Duration) Hours() int64\nfunc (dur Duration) Days() int\nfunc (dur Duration) Weeks() int\nfunc (dur Duration) Months() int\nfunc (dur Duration) Years() int\n\n// Float methods (precise)\nfunc (dur Duration) MonthsFloat() float64\nfunc (dur Duration) YearsFloat() float64\n```\n\n**Precision:**\n- Integer variants return rounded-down values\n- Float variants for precise calculations\n- Handle negative differences (date1 \u003c date2)\n- Cross year boundaries correctly\n- Handle leap years correctly\n\n## Acceptance Criteria\n- [ ] Duration type defined\n- [ ] Diff(a, b) returns Duration\n- [ ] All integer methods implemented (Seconds through Years)\n- [ ] Float methods for Months and Years implemented\n- [ ] Negative differences handled correctly\n- [ ] Calculations correct across year boundaries\n- [ ] Leap year handling correct\n- [ ] Unit tests with 95%+ coverage\n- [ ] Table-driven tests for various date ranges\n- [ ] Benchmarks meet \u003c1µs (int) and \u003c2µs (float) targets\n- [ ] Godoc comments with precision explanation","status":"closed","priority":1,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-02-11T16:21:06.159742785+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-02-11T17:38:26.443877375+01:00","closed_at":"2026-02-11T17:38:26.443877375+01:00","close_reason":"Closed","dependencies":[{"issue_id":"quando-ljj","depends_on_id":"quando-j2s","type":"blocks","created_at":"2026-02-11T16:23:08.127266864+01:00","created_by":"Oliver Jakoubek"}],"comments":[{"id":7,"issue_id":"quando-ljj","author":"Oliver Jakoubek","text":"Plan: 1) Create diff.go with Duration type (private fields: start, end time.Time), 2) Implement Diff(a, b) package function, 3) Implement integer methods (Seconds through Years) with rounded-down values, 4) Implement float methods (MonthsFloat, YearsFloat) for precise calculations, 5) Handle negative differences (when a \u003c b), 6) Correctly handle year boundaries and leap years in month/year calculations, 7) Write comprehensive table-driven tests covering edge cases, 8) Add benchmarks, 9) Godoc comments with precision explanation","created_at":"2026-02-11T16:34:17Z"}]}
|
||||||
{"id":"quando-r1o","title":"Comprehensive test suite and benchmarks","description":"Ensure comprehensive test coverage (95%+) and performance benchmarks for all features.\n\n**Test Requirements:**\n- Minimum 95% coverage for all calculation functions\n- Table-driven tests for edge cases\n- Example tests for godoc\n- Separate benchmark file\n\n**Critical Test Scenarios:**\n1. Month arithmetic: Overflow, leap years, negative\n2. Snap operations: All units, edge cases\n3. Next/Prev: Same weekday edge case\n4. Diff calculations: Year boundaries, leap years, negative\n5. DST handling: Add across DST transitions\n6. Parsing: All formats, ambiguous, invalid\n7. WeekNumber: ISO 8601 compliance\n8. Formatting: All formats, i18n\n\n**Benchmarks:**\nTarget performance:\n- Add/Sub: \u003c1µs\n- Diff: \u003c1µs (int), \u003c2µs (float)\n- Format: \u003c5µs (no i18n), \u003c10µs (with i18n)\n- Parse: \u003c10µs (auto), \u003c20µs (relative)\n\n## Acceptance Criteria\n- [ ] Test coverage ≥95% for all calculation functions\n- [ ] Table-driven tests for all edge cases\n- [ ] Example tests in example_test.go\n- [ ] Benchmark file with all critical operations\n- [ ] All benchmarks meet performance targets\n- [ ] CI/CD runs tests and reports coverage\n- [ ] Tests use FixedClock for determinism\n- [ ] Edge cases documented in test names/comments","status":"open","priority":3,"issue_type":"task","owner":"mail@oliverjakoubek.de","created_at":"2026-02-11T16:22:38.100200304+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-02-11T16:22:38.100200304+01:00","dependencies":[{"issue_id":"quando-r1o","depends_on_id":"quando-b4r","type":"blocks","created_at":"2026-02-11T16:23:15.418358186+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"quando-r1o","depends_on_id":"quando-dsx","type":"blocks","created_at":"2026-02-11T16:23:15.474799177+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"quando-r1o","depends_on_id":"quando-9sf","type":"blocks","created_at":"2026-02-11T16:23:15.513811922+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"quando-r1o","depends_on_id":"quando-10t","type":"blocks","created_at":"2026-02-11T16:23:15.545561332+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"quando-r1o","depends_on_id":"quando-gr5","type":"blocks","created_at":"2026-02-11T16:23:15.578273993+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"quando-r1o","depends_on_id":"quando-5ol","type":"blocks","created_at":"2026-02-11T16:23:15.609031891+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"quando-r1o","depends_on_id":"quando-5ib","type":"blocks","created_at":"2026-02-11T16:23:15.640381484+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"quando-r1o","depends_on_id":"quando-41g","type":"blocks","created_at":"2026-02-11T16:23:15.672161997+01:00","created_by":"Oliver Jakoubek"}]}
|
{"id":"quando-r1o","title":"Comprehensive test suite and benchmarks","description":"Ensure comprehensive test coverage (95%+) and performance benchmarks for all features.\n\n**Test Requirements:**\n- Minimum 95% coverage for all calculation functions\n- Table-driven tests for edge cases\n- Example tests for godoc\n- Separate benchmark file\n\n**Critical Test Scenarios:**\n1. Month arithmetic: Overflow, leap years, negative\n2. Snap operations: All units, edge cases\n3. Next/Prev: Same weekday edge case\n4. Diff calculations: Year boundaries, leap years, negative\n5. DST handling: Add across DST transitions\n6. Parsing: All formats, ambiguous, invalid\n7. WeekNumber: ISO 8601 compliance\n8. Formatting: All formats, i18n\n\n**Benchmarks:**\nTarget performance:\n- Add/Sub: \u003c1µs\n- Diff: \u003c1µs (int), \u003c2µs (float)\n- Format: \u003c5µs (no i18n), \u003c10µs (with i18n)\n- Parse: \u003c10µs (auto), \u003c20µs (relative)\n\n## Acceptance Criteria\n- [ ] Test coverage ≥95% for all calculation functions\n- [ ] Table-driven tests for all edge cases\n- [ ] Example tests in example_test.go\n- [ ] Benchmark file with all critical operations\n- [ ] All benchmarks meet performance targets\n- [ ] CI/CD runs tests and reports coverage\n- [ ] Tests use FixedClock for determinism\n- [ ] Edge cases documented in test names/comments","status":"open","priority":3,"issue_type":"task","owner":"mail@oliverjakoubek.de","created_at":"2026-02-11T16:22:38.100200304+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-02-11T16:22:38.100200304+01:00","dependencies":[{"issue_id":"quando-r1o","depends_on_id":"quando-b4r","type":"blocks","created_at":"2026-02-11T16:23:15.418358186+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"quando-r1o","depends_on_id":"quando-dsx","type":"blocks","created_at":"2026-02-11T16:23:15.474799177+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"quando-r1o","depends_on_id":"quando-9sf","type":"blocks","created_at":"2026-02-11T16:23:15.513811922+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"quando-r1o","depends_on_id":"quando-10t","type":"blocks","created_at":"2026-02-11T16:23:15.545561332+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"quando-r1o","depends_on_id":"quando-gr5","type":"blocks","created_at":"2026-02-11T16:23:15.578273993+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"quando-r1o","depends_on_id":"quando-5ol","type":"blocks","created_at":"2026-02-11T16:23:15.609031891+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"quando-r1o","depends_on_id":"quando-5ib","type":"blocks","created_at":"2026-02-11T16:23:15.640381484+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"quando-r1o","depends_on_id":"quando-41g","type":"blocks","created_at":"2026-02-11T16:23:15.672161997+01:00","created_by":"Oliver Jakoubek"}]}
|
||||||
{"id":"quando-tn3","title":"Relative date parsing","description":"Implement ParseRelative for parsing relative date expressions.\n\n**API:**\n```go\nfunc ParseRelative(s string) (Date, error)\n```\n\n**Supported Expressions (Phase 1):**\n- \"today\" → Today 00:00:00\n- \"tomorrow\" → Tomorrow 00:00:00\n- \"yesterday\" → Yesterday 00:00:00\n- \"+2 days\" → Today + 2 days\n- \"-1 week\" → Today - 1 week\n- \"+3 months\" → Today + 3 months\n\n**Format:**\n- Relative offsets: [+|-]\u003cnumber\u003e \u003cunit\u003e\n- Units: day(s), week(s), month(s), quarter(s), year(s)\n- Singular and plural forms supported\n\n**Out of Scope (Phase 1):**\n- Complex expressions (\"next monday\", \"start of month\")\n- These are nice-to-have for later versions\n\n## Acceptance Criteria\n- [ ] ParseRelative() implemented\n- [ ] \"today\" returns today 00:00:00\n- [ ] \"tomorrow\" returns tomorrow 00:00:00\n- [ ] \"yesterday\" returns yesterday 00:00:00\n- [ ] \"+N \u003cunit\u003e\" adds N units to today\n- [ ] \"-N \u003cunit\u003e\" subtracts N units from today\n- [ ] Singular and plural units both work\n- [ ] All Phase 1 units supported (days, weeks, months, quarters, years)\n- [ ] Invalid expressions return clear errors\n- [ ] Never panics\n- [ ] Unit tests for all supported expressions\n- [ ] Unit tests for invalid inputs\n- [ ] Benchmark meets \u003c20µs target\n- [ ] Godoc with supported expressions listed\n- [ ] Note about future complex expressions","status":"open","priority":2,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-02-11T16:21:40.790156181+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-02-11T16:21:40.790156181+01:00","dependencies":[{"issue_id":"quando-tn3","depends_on_id":"quando-j2s","type":"blocks","created_at":"2026-02-11T16:23:11.247985003+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"quando-tn3","depends_on_id":"quando-36t","type":"blocks","created_at":"2026-02-11T16:23:11.278394998+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"quando-tn3","depends_on_id":"quando-b4r","type":"blocks","created_at":"2026-02-11T16:23:11.309549914+01:00","created_by":"Oliver Jakoubek"}]}
|
{"id":"quando-tn3","title":"Relative date parsing","description":"Implement ParseRelative for parsing relative date expressions.\n\n**API:**\n```go\nfunc ParseRelative(s string) (Date, error)\n```\n\n**Supported Expressions (Phase 1):**\n- \"today\" → Today 00:00:00\n- \"tomorrow\" → Tomorrow 00:00:00\n- \"yesterday\" → Yesterday 00:00:00\n- \"+2 days\" → Today + 2 days\n- \"-1 week\" → Today - 1 week\n- \"+3 months\" → Today + 3 months\n\n**Format:**\n- Relative offsets: [+|-]\u003cnumber\u003e \u003cunit\u003e\n- Units: day(s), week(s), month(s), quarter(s), year(s)\n- Singular and plural forms supported\n\n**Out of Scope (Phase 1):**\n- Complex expressions (\"next monday\", \"start of month\")\n- These are nice-to-have for later versions\n\n## Acceptance Criteria\n- [ ] ParseRelative() implemented\n- [ ] \"today\" returns today 00:00:00\n- [ ] \"tomorrow\" returns tomorrow 00:00:00\n- [ ] \"yesterday\" returns yesterday 00:00:00\n- [ ] \"+N \u003cunit\u003e\" adds N units to today\n- [ ] \"-N \u003cunit\u003e\" subtracts N units from today\n- [ ] Singular and plural units both work\n- [ ] All Phase 1 units supported (days, weeks, months, quarters, years)\n- [ ] Invalid expressions return clear errors\n- [ ] Never panics\n- [ ] Unit tests for all supported expressions\n- [ ] Unit tests for invalid inputs\n- [ ] Benchmark meets \u003c20µs target\n- [ ] Godoc with supported expressions listed\n- [ ] Note about future complex expressions","status":"open","priority":2,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-02-11T16:21:40.790156181+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-02-11T16:21:40.790156181+01:00","dependencies":[{"issue_id":"quando-tn3","depends_on_id":"quando-j2s","type":"blocks","created_at":"2026-02-11T16:23:11.247985003+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"quando-tn3","depends_on_id":"quando-36t","type":"blocks","created_at":"2026-02-11T16:23:11.278394998+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"quando-tn3","depends_on_id":"quando-b4r","type":"blocks","created_at":"2026-02-11T16:23:11.309549914+01:00","created_by":"Oliver Jakoubek"}]}
|
||||||
{"id":"quando-vih","title":"Clock abstraction for testability","description":"Implement Clock interface to enable dependency injection and deterministic testing.\n\n**Technical Details:**\n```go\ntype Clock interface {\n Now() Date\n From(t time.Time) Date\n}\n```\n\n**Implementations:**\n- DefaultClock: Uses time.Now()\n- FixedClock: Returns fixed time for tests\n\n**API:**\n- `NewClock()` - returns DefaultClock\n- `NewFixedClock(time.Time)` - returns FixedClock for tests\n\n## Acceptance Criteria\n- [ ] Clock interface defined\n- [ ] DefaultClock implementation using time.Now()\n- [ ] FixedClock implementation with fixed time\n- [ ] NewClock() factory function\n- [ ] NewFixedClock(time.Time) factory function\n- [ ] Unit tests demonstrating deterministic test patterns\n- [ ] Godoc comments\n- [ ] Example test showing test usage pattern","status":"closed","priority":0,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-02-11T16:20:33.357927572+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-02-11T16:33:26.936900547+01:00","closed_at":"2026-02-11T16:33:26.936900547+01:00","close_reason":"Closed","dependencies":[{"issue_id":"quando-vih","depends_on_id":"quando-91w","type":"blocks","created_at":"2026-02-11T16:23:05.308383809+01:00","created_by":"Oliver Jakoubek"}],"comments":[{"id":3,"issue_id":"quando-vih","author":"Oliver Jakoubek","text":"Plan: 1) Create clock.go with Clock interface (Now, From methods), 2) Implement DefaultClock using time.Now(), 3) Implement FixedClock with fixed time for deterministic tests, 4) Add factory functions NewClock() and NewFixedClock(time.Time), 5) Write comprehensive unit tests demonstrating deterministic test patterns, 6) Add example tests showing test usage, 7) Ensure all exports have godoc comments","created_at":"2026-02-11T15:31:45Z"}]}
|
{"id":"quando-vih","title":"Clock abstraction for testability","description":"Implement Clock interface to enable dependency injection and deterministic testing.\n\n**Technical Details:**\n```go\ntype Clock interface {\n Now() Date\n From(t time.Time) Date\n}\n```\n\n**Implementations:**\n- DefaultClock: Uses time.Now()\n- FixedClock: Returns fixed time for tests\n\n**API:**\n- `NewClock()` - returns DefaultClock\n- `NewFixedClock(time.Time)` - returns FixedClock for tests\n\n## Acceptance Criteria\n- [ ] Clock interface defined\n- [ ] DefaultClock implementation using time.Now()\n- [ ] FixedClock implementation with fixed time\n- [ ] NewClock() factory function\n- [ ] NewFixedClock(time.Time) factory function\n- [ ] Unit tests demonstrating deterministic test patterns\n- [ ] Godoc comments\n- [ ] Example test showing test usage pattern","status":"closed","priority":0,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-02-11T16:20:33.357927572+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-02-11T16:33:26.936900547+01:00","closed_at":"2026-02-11T16:33:26.936900547+01:00","close_reason":"Closed","dependencies":[{"issue_id":"quando-vih","depends_on_id":"quando-91w","type":"blocks","created_at":"2026-02-11T16:23:05.308383809+01:00","created_by":"Oliver Jakoubek"}],"comments":[{"id":3,"issue_id":"quando-vih","author":"Oliver Jakoubek","text":"Plan: 1) Create clock.go with Clock interface (Now, From methods), 2) Implement DefaultClock using time.Now(), 3) Implement FixedClock with fixed time for deterministic tests, 4) Add factory functions NewClock() and NewFixedClock(time.Time), 5) Write comprehensive unit tests demonstrating deterministic test patterns, 6) Add example tests showing test usage, 7) Ensure all exports have godoc comments","created_at":"2026-02-11T15:31:45Z"}]}
|
||||||
|
|
|
||||||
79
errors.go
Normal file
79
errors.go
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
package quando
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
// Error Handling Policy
|
||||||
|
//
|
||||||
|
// The quando library follows a strict no-panic policy for all normal operations.
|
||||||
|
// Errors are always returned as values, allowing calling code to handle them
|
||||||
|
// appropriately.
|
||||||
|
//
|
||||||
|
// Exception: Must* functions (e.g., MustParse) are provided for convenience
|
||||||
|
// in tests and initialization code. These functions panic on error and should
|
||||||
|
// never be used in production code where errors need to be handled gracefully.
|
||||||
|
//
|
||||||
|
// Error Types
|
||||||
|
//
|
||||||
|
// The library defines sentinel errors for common error conditions. Use errors.Is()
|
||||||
|
// to check for specific error types:
|
||||||
|
//
|
||||||
|
// date, err := quando.Parse("invalid")
|
||||||
|
// if errors.Is(err, quando.ErrInvalidFormat) {
|
||||||
|
// // Handle invalid format
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// For errors with context, use fmt.Errorf with %w to wrap errors:
|
||||||
|
//
|
||||||
|
// return Date{}, fmt.Errorf("parsing date %q: %w", input, quando.ErrInvalidFormat)
|
||||||
|
|
||||||
|
// ErrInvalidFormat indicates that a date string could not be parsed.
|
||||||
|
//
|
||||||
|
// This error is returned when:
|
||||||
|
// - The input string doesn't match any supported format
|
||||||
|
// - The format is ambiguous (e.g., "01/02/2026" without year prefix)
|
||||||
|
// - The date components are invalid (e.g., "2026-13-01" for month 13)
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// date, err := quando.Parse("not-a-date")
|
||||||
|
// if errors.Is(err, quando.ErrInvalidFormat) {
|
||||||
|
// log.Printf("Invalid date format: %v", err)
|
||||||
|
// }
|
||||||
|
var ErrInvalidFormat = errors.New("invalid date format")
|
||||||
|
|
||||||
|
// ErrInvalidTimezone indicates that a timezone name is not recognized.
|
||||||
|
//
|
||||||
|
// This error is returned when:
|
||||||
|
// - The IANA timezone name is not found in the system timezone database
|
||||||
|
// - The timezone string is malformed
|
||||||
|
//
|
||||||
|
// Valid timezone names include "UTC", "America/New_York", "Europe/Berlin", etc.
|
||||||
|
// See the IANA Time Zone Database for a complete list.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// date, err := quando.Now().InTimezone("Invalid/Timezone")
|
||||||
|
// if errors.Is(err, quando.ErrInvalidTimezone) {
|
||||||
|
// log.Printf("Unknown timezone: %v", err)
|
||||||
|
// }
|
||||||
|
var ErrInvalidTimezone = errors.New("invalid timezone")
|
||||||
|
|
||||||
|
// ErrOverflow indicates that a date arithmetic operation would result in
|
||||||
|
// a date outside the representable range.
|
||||||
|
//
|
||||||
|
// This error is returned when:
|
||||||
|
// - Adding/subtracting would exceed Go's time.Time range (approximately year 0 to 9999)
|
||||||
|
// - The resulting date would be before the minimum or after the maximum representable time
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// date := quando.From(time.Date(9999, 12, 31, 0, 0, 0, 0, time.UTC))
|
||||||
|
// result, err := date.Add(1, quando.Years)
|
||||||
|
// if errors.Is(err, quando.ErrOverflow) {
|
||||||
|
// log.Printf("Date overflow: %v", err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Note: In the current implementation, arithmetic operations that would overflow
|
||||||
|
// are handled by Go's time.Time, which has its own overflow behavior. This error
|
||||||
|
// is reserved for future use when explicit overflow detection is added.
|
||||||
|
var ErrOverflow = errors.New("date overflow")
|
||||||
73
errors_test.go
Normal file
73
errors_test.go
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
package quando
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestSentinelErrors verifies that all sentinel errors are defined
|
||||||
|
func TestSentinelErrors(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
check string
|
||||||
|
}{
|
||||||
|
{"ErrInvalidFormat", ErrInvalidFormat, "invalid date format"},
|
||||||
|
{"ErrInvalidTimezone", ErrInvalidTimezone, "invalid timezone"},
|
||||||
|
{"ErrOverflow", ErrOverflow, "date overflow"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.err == nil {
|
||||||
|
t.Errorf("%s is nil, expected error", tt.name)
|
||||||
|
}
|
||||||
|
if tt.err.Error() != tt.check {
|
||||||
|
t.Errorf("%s.Error() = %q, want %q", tt.name, tt.err.Error(), tt.check)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestErrorsIs verifies that errors.Is works with sentinel errors
|
||||||
|
func TestErrorsIs(t *testing.T) {
|
||||||
|
// Test direct comparison
|
||||||
|
if !errors.Is(ErrInvalidFormat, ErrInvalidFormat) {
|
||||||
|
t.Error("errors.Is(ErrInvalidFormat, ErrInvalidFormat) should be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !errors.Is(ErrInvalidTimezone, ErrInvalidTimezone) {
|
||||||
|
t.Error("errors.Is(ErrInvalidTimezone, ErrInvalidTimezone) should be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !errors.Is(ErrOverflow, ErrOverflow) {
|
||||||
|
t.Error("errors.Is(ErrOverflow, ErrOverflow) should be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that different errors are not equal
|
||||||
|
if errors.Is(ErrInvalidFormat, ErrInvalidTimezone) {
|
||||||
|
t.Error("ErrInvalidFormat and ErrInvalidTimezone should not be equal")
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(ErrInvalidTimezone, ErrOverflow) {
|
||||||
|
t.Error("ErrInvalidTimezone and ErrOverflow should not be equal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestErrorUniqueness verifies that each error is unique
|
||||||
|
func TestErrorUniqueness(t *testing.T) {
|
||||||
|
allErrors := []error{
|
||||||
|
ErrInvalidFormat,
|
||||||
|
ErrInvalidTimezone,
|
||||||
|
ErrOverflow,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that no two errors are the same
|
||||||
|
for i, err1 := range allErrors {
|
||||||
|
for j, err2 := range allErrors {
|
||||||
|
if i != j && err1 == err2 {
|
||||||
|
t.Errorf("Errors at index %d and %d are the same", i, j)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package quando_test
|
package quando_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -277,3 +278,37 @@ func ExampleDuration_negative() {
|
||||||
// Months: -12
|
// Months: -12
|
||||||
// Years: -1
|
// Years: -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExampleErrInvalidFormat demonstrates handling invalid date formats
|
||||||
|
func Example_errorHandling() {
|
||||||
|
// Note: Parse doesn't exist yet, so this is a conceptual example
|
||||||
|
// showing the error handling pattern that will be used
|
||||||
|
|
||||||
|
// Simulate an error by directly using the sentinel error
|
||||||
|
err := quando.ErrInvalidFormat
|
||||||
|
|
||||||
|
// Check for specific error type
|
||||||
|
if errors.Is(err, quando.ErrInvalidFormat) {
|
||||||
|
fmt.Println("Invalid format detected")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output: Invalid format detected
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example_errorTypes demonstrates all error types
|
||||||
|
func Example_errorTypes() {
|
||||||
|
// Show all defined error types
|
||||||
|
errors := []error{
|
||||||
|
quando.ErrInvalidFormat,
|
||||||
|
quando.ErrInvalidTimezone,
|
||||||
|
quando.ErrOverflow,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, err := range errors {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
}
|
||||||
|
// Output:
|
||||||
|
// invalid date format
|
||||||
|
// invalid timezone
|
||||||
|
// date overflow
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue