Skip to content

Commit 851524f

Browse files
Flyrellclaude
andcommitted
feat: allow all three --from/--to/--duration flags in edit when consistent
Previously, specifying --duration, --from, and --to together was rejected outright. Now all three are accepted as long as they are consistent (to - from = duration), giving users explicit control without ambiguity. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 62c96b4 commit 851524f

4 files changed

Lines changed: 50 additions & 10 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ hourgit edit <hash> [--duration <dur>] [--from <time>] [--to <time>] [--date <da
184184
| `-m`, `--message` || New message |
185185
| `-y`, `--yes` | `false` | Skip confirmation prompt |
186186

187-
> Any two of `--duration`, `--from`, and `--to` can be combined (the third is computed), but all three cannot be specified together. `--from` only: keeps duration, shifts the time window. `--to` only: keeps start, recalculates duration. `--duration` only: keeps start, shifts end. Entry ID and creation timestamp are preserved. If the entry is not found in the current repo's project, all projects are searched.
187+
> Any combination of `--duration`, `--from`, and `--to` is allowed. With two flags, the third is computed. With all three, they must be consistent (to - from = duration). `--from` only: keeps duration, shifts the time window. `--to` only: keeps start, recalculates duration. `--duration` only: keeps start, shifts end. Entry ID and creation timestamp are preserved. If the entry is not found in the current repo's project, all projects are searched.
188188
189189
**Examples**
190190

internal/cli/edit.go

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -212,11 +212,6 @@ func applyFlagEdits(
212212
hasTo := flagsChanged["to"]
213213
hasDate := flagsChanged["date"]
214214

215-
// Over-specified: all three time flags
216-
if hasDuration && hasFrom && hasTo {
217-
return e, fmt.Errorf("--duration, --from, and --to cannot all be specified together")
218-
}
219-
220215
// Handle date shift
221216
if hasDate {
222217
newDate, err := resolveBaseDate(dateFlag, nowFn())
@@ -230,6 +225,34 @@ func applyFlagEdits(
230225

231226
// Handle time changes — interdependent from/to/duration
232227
switch {
228+
case hasDuration && hasFrom && hasTo:
229+
// All three specified — validate consistency: to - from must equal duration
230+
fromTOD, err := schedule.ParseTimeOfDay(fromFlag)
231+
if err != nil {
232+
return e, fmt.Errorf("invalid --from time: %w", err)
233+
}
234+
toTOD, err := schedule.ParseTimeOfDay(toFlag)
235+
if err != nil {
236+
return e, fmt.Errorf("invalid --to time: %w", err)
237+
}
238+
minutes, err := entry.ParseDuration(durationFlag)
239+
if err != nil {
240+
return e, err
241+
}
242+
fromMins := fromTOD.Hour*60 + fromTOD.Minute
243+
toMins := toTOD.Hour*60 + toTOD.Minute
244+
if toMins <= fromMins {
245+
return e, fmt.Errorf("--to (%s) must be after --from (%s)", toFlag, fromFlag)
246+
}
247+
computed := toMins - fromMins
248+
if computed != minutes {
249+
return e, fmt.Errorf("--duration (%s) does not match --from/%s to --to/%s (%s)",
250+
durationFlag, fromFlag, toFlag, entry.FormatMinutes(computed))
251+
}
252+
y, m, d := e.Start.Date()
253+
e.Start = time.Date(y, m, d, fromTOD.Hour, fromTOD.Minute, 0, 0, e.Start.Location())
254+
e.Minutes = minutes
255+
233256
case hasDuration && hasFrom:
234257
// --duration --from → set from, set minutes (to = from + duration)
235258
fromTOD, err := schedule.ParseTimeOfDay(fromFlag)

internal/cli/edit_test.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -243,14 +243,31 @@ func TestEditDurationAndTo(t *testing.T) {
243243
assert.Equal(t, 120, e.Minutes)
244244
}
245245

246-
func TestEditDurationFromToOverSpecified(t *testing.T) {
246+
func TestEditAllThreeConsistent(t *testing.T) {
247+
homeDir, repoDir, proj, _ := setupEditTest(t)
248+
249+
// --from 10am --to 12pm --duration 2h → consistent, should succeed
250+
flags := map[string]bool{"duration": true, "from": true, "to": true}
251+
stdout, err := execEdit(homeDir, repoDir, "", "ed01234", flags, "2h", "10am", "12pm", "", "", "")
252+
253+
require.NoError(t, err)
254+
assert.Contains(t, stdout, "updated entry")
255+
256+
e, err := entry.ReadEntry(homeDir, proj.Slug, "ed01234")
257+
require.NoError(t, err)
258+
assert.Equal(t, 10, e.Start.Hour())
259+
assert.Equal(t, 120, e.Minutes)
260+
}
261+
262+
func TestEditAllThreeInconsistent(t *testing.T) {
247263
homeDir, repoDir, _, _ := setupEditTest(t)
248264

265+
// --from 10am --to 12pm --duration 3h → inconsistent (2h ≠ 3h)
249266
flags := map[string]bool{"duration": true, "from": true, "to": true}
250-
_, err := execEdit(homeDir, repoDir, "", "ed01234", flags, "2h", "9am", "11am", "", "", "")
267+
_, err := execEdit(homeDir, repoDir, "", "ed01234", flags, "3h", "10am", "12pm", "", "", "")
251268

252269
assert.Error(t, err)
253-
assert.Contains(t, err.Error(), "cannot all be specified")
270+
assert.Contains(t, err.Error(), "does not match")
254271
}
255272

256273
func TestEditToBeforeStart(t *testing.T) {

web/docs/commands/time-tracking.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ hourgit edit <hash> [--duration <dur>] [--from <time>] [--to <time>] [--date <da
7373
| `-m`, `--message` || New message |
7474
| `-y`, `--yes` | `false` | Skip confirmation prompt |
7575

76-
> Any two of `--duration`, `--from`, and `--to` can be combined (the third is computed), but all three cannot be specified together. `--from` only: keeps duration, shifts the time window. `--to` only: keeps start, recalculates duration. `--duration` only: keeps start, shifts end. Entry ID and creation timestamp are preserved.
76+
> Any combination of `--duration`, `--from`, and `--to` is allowed. With two flags, the third is computed. With all three, they must be consistent (to - from = duration). `--from` only: keeps duration, shifts the time window. `--to` only: keeps start, recalculates duration. `--duration` only: keeps start, shifts end. Entry ID and creation timestamp are preserved.
7777
7878
**Examples:**
7979

0 commit comments

Comments
 (0)