Skip to content

feat: export#45

Merged
Pertempto merged 13 commits intomainfrom
feat/export
Jan 9, 2026
Merged

feat: export#45
Pertempto merged 13 commits intomainfrom
feat/export

Conversation

@Pertempto
Copy link
Contributor

@Pertempto Pertempto commented Jan 7, 2026

Implemented the export command according to spec 6

Testing

First run just build-docker

Note

The output of the export command on stdout might look funny (misaligned). That is because tabs are often interpreted as just taking the text to the next "tab stop" (usually the next multiple of 8). The key thing is that the fields are correctly separated by tabs. You can pipe the output of the export command to od -c to see raw characters, or pipe to cat -A which will show tabs as ^I.

Daily Projects

  • just run-docker export outputs all the days with their projects, durations, and descriptions

Raw

  • just run-docker export -f raw outputs all the entries with their project, task, start, end, and duration

Output to File

To test the -o option, you will have to install/build the actual app, running it inside the docker container will just write a file inside the docker container

- Add ExportDailyProjects and ExportRaw functions with proper TSV formatting
- Use encoding/csv with tab separator for correct escaping of special characters
- Filter blank entries and exclude running entries from exports
- Add export CLI command with --format (daily-projects|raw) and --output flags
- Comprehensive test coverage for both formats including special character handling
- Update spec 006 to mark all tasks as complete
@github-actions

This comment was marked as resolved.

@github-actions

This comment was marked as resolved.

@github-actions

This comment was marked as resolved.

@github-actions
Copy link

github-actions bot commented Jan 7, 2026

Changes Requested

Please make the following small changes before I can approve:

  • Replace fmt.Println(exportData) in cmd/export_cmd.go with fmt.Fprint(os.Stdout, exportData) (or os.Stdout.WriteString) so the command does not add an extra trailing newline to the TSV output.
  • Remove the redundant if entry.End == nil { continue } in utils/aggregation.go (the earlier entry.IsRunning() check already covers this), or add a brief comment explaining the defensive duplication.
  • Clarify timezone intent for ProjectDateEntry.Date in utils/aggregation.go: either parse with the entry's location (e.g. time.ParseInLocation("2006-01-02", dateStr, entry.Start.Location())) or expand the comment to state why normalizing to UTC midnight is desired and how it affects local-date consumers.

Summary of Changes

  • Added new export command (cmd/export_cmd.go) with --format and --output flags.
  • Implemented ExportDailyProjects and ExportRaw in utils/export.go, returning errors on write/flush failures.
  • Added comprehensive TSV tests in utils/export_test.go covering special characters, filtering, and rounding.
  • Updated AggregateByProjectDate in utils/aggregation.go to skip running/blank entries and normalize date parsing.
  • Marked the export spec specs/006-export.md as completed.

Overall Feedback

  • Issues / Concerns (please address):

    • The CLI currently prints TSV to stdout with fmt.Println, which appends an extra newline. That can break exact-match tests or downstream parsers — switch to fmt.Fprint.
    • AggregateByProjectDate contains a redundant nil-check after IsRunning(); simplify or document the defensive pattern to avoid confusion.
    • Timezone handling for aggregated dates is potentially surprising: parsing the date string using time.Parse yields a UTC midnight time. Please confirm intended semantics and either preserve the entry's timezone when parsing or document the UTC normalization.
  • Positive: The change set is well-tested and thorough. Error handling for CSV writes/flushes has been implemented and the export tests cover edge cases (tabs, newlines, quotes, rounding). The CLI flag handling and file-permission choice (0600) are thoughtful for privacy. Nice work — tests and structure make this easy to review. 🎉

@Pertempto — once you address the three small items above I will re-review quickly. Thanks!

@Pertempto Pertempto self-assigned this Jan 7, 2026
@Pertempto Pertempto requested a review from bambam955 January 7, 2026 20:24
- Change ExportDailyProjects and ExportRaw to return (string, error)
- Capture and propagate csv.Write errors with context
- Check writer.Error() after Flush to catch encoding errors
- Update all tests to handle error returns
- Update cmd/export.go to propagate errors to CLI exit code
- Reason: Previously encoding errors were silently dropped, potentially
  causing empty/corrupted output without user notification
- Use fmt.Println for stdout output to ensure trailing newline for shell pipeline compatibility
- Change file permissions from 0644 to 0600 (owner read/write only) for privacy
  of sensitive time tracking data, especially important in Docker/CI environments
- Add comments explaining the rationale for each change
- Add defensive nil check for entry.End in AggregateByProjectDate
  (IsRunning() guard should guarantee non-nil, but defensive check prevents
  potential panics if function is called from elsewhere)
- Remove unused Now variable (tests don't depend on it)
- Document timezone behavior: ProjectDateEntry.Date uses UTC from time.Parse,
  intentional to align with date display in stats
- Add comment clarifying that date parsing is safe
@Pertempto
Copy link
Contributor Author

Pushed some commits to address feedback from code review bot.

@github-actions

This comment was marked as resolved.

- Replace fmt.Println with os.Stdout.WriteString to avoid appending extra newline
- Preserves exact TSV output without blank line for accurate downstream parsing
- Add error handling for WriteString call
- Reason: fmt.Println adds unwanted blank line that breaks TSV parsing expectations
…gregation

- Change time.Parse to time.ParseInLocation with entry.Start.Location()
- Ensures ProjectDateEntry.Date respects the entry's timezone
- "2025-01-01" in user's timezone becomes midnight in that timezone, not UTC
- Update docstring to clarify timezone preservation behavior
- Improves alignment with local timezone display expectations in stats and exports
- Revert ParseInLocation change, use time.Parse which returns UTC
- Entries are stored in UTC, so aggregation should use UTC for consistency
- Clarify that display layer (TUI/export) handles timezone conversion
- Ensure consistent date-based aggregation regardless of system timezone
@Pertempto Pertempto marked this pull request as ready for review January 7, 2026 20:39
@Pertempto
Copy link
Contributor Author

@bambam955 no need to do in-depth code review for this one. mostly just looking for a sanity check with the testing

@bambam955
Copy link
Contributor

I still don't understand how the Docker part works:

bennett-moore@desktop:~/projects/open-source/time-tracker (feat/export)$ just build-docker
docker compose build
[+] Building 0.1s (5/5) FINISHED                                                                                                                                                                                                              
 => [internal] load local bake definitions                                                                                                                                                                                               0.0s
 => => reading from stdin 1.06kB                                                                                                                                                                                                         0.0s
 => [test internal] load build definition from Dockerfile.test                                                                                                                                                                           0.0s
 => => transferring dockerfile: 2B                                                                                                                                                                                                       0.0s
 => [time-tracker internal] load build definition from Dockerfile                                                                                                                                                                        0.0s
 => => transferring dockerfile: 429B                                                                                                                                                                                                     0.0s
 => CANCELED [time-tracker internal] load metadata for docker.io/library/alpine:latest                                                                                                                                                   0.0s
 => CANCELED [time-tracker internal] load metadata for docker.io/library/golang:1.24                                                                                                                                                     0.0s
[+] build 0/2
 ⠙ Image time-tracker      Building                                                                                                                                                                                                      0.2s 
 ⠙ Image time-tracker-test Building                                                                                                                                                                                                      0.2s 
target test: failed to solve: failed to read dockerfile: open Dockerfile.test: no such file or directory

error: Recipe `build-docker` failed on line 25 with exit code 1

But until this is resolved I can't actually test anything with the steps given.

@Pertempto
Copy link
Contributor Author

Ah, no I think there is something broke with that recipe. It got fixed in another branch. I'll bring that fix over here. 👍🏽

@Pertempto
Copy link
Contributor Author

Actually it was a problem in docker-compose.yml referencing non-existent Dockerfile.test

@Pertempto
Copy link
Contributor Author

@bambam955 It should work now

@bambam955
Copy link
Contributor

bambam955 commented Jan 9, 2026

@Pertempto code looks fine and it tests well. At first I was wishing there was something like time-tracker export -f pretty or something, but that's what the stats subcommand is for.

Merge whenever 👍

@Pertempto
Copy link
Contributor Author

At first I was wishing there was something like time-tracker export -f pretty or something, but that's what the stats subcommand is for.

Correct. This command is purely for data export. We have the stats command and stats view in the TUI (press tab) to display stats. We should consider making the stats command have output that matches the stats view in the TUI.

@Pertempto Pertempto merged commit de57b3d into main Jan 9, 2026
1 check passed
@Pertempto Pertempto deleted the feat/export branch January 9, 2026 18:16
Pertempto added a commit that referenced this pull request Jan 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants