Important: AI-generated, obviously.
Garmin has all the data in the world, but sometimes still fails to apply even basic sanity checks.
A typical example: I am swimming in a 50 m pool. Most of my lengths are around 60 seconds and 30 strokes. Yes, I know, you are much faster than me, and I need to work on my technique. I keep that pace fairly consistently for 1.5 km. Then, halfway through one length, I catch up with two slower swimmers, pause for a couple of seconds, and try to get around them.
Garmin’s conclusion: "Good job. That was not 50 m. That was 100 m."
Suddenly the activity contains two impossible 50 m lengths: roughly 30 seconds and 15 strokes each. I am very fast. No?
What does Garmin recommend? Improve your technique. Great idea, though.
At that point I am annoyed enough to throw the Garmin away and switch to an Apple Watch. Or, probably smarter, stop caring about all these smart-device metrics and just swim for enjoyment. Unfortunately, I have the bad habit of caring about numbers.
I first found Swimming Watch Tools, which was later deprecated and handed over to swimdata.org. Since I prefer CLI tools, I decided to write my own.
This tool repairs Garmin pool-swim FIT files. It targets impossible active lengths caused by bad pool-swim detection: stroke changes, pauses, turns, exits, partial movement, or sensor glitches that Garmin turns into extra lengths.
Garmin Connect can adjust the total distance, but the original bad length records may remain inside the FIT activity and continue to distort best pace, stroke totals, SWOLF, and personal-record signals.
Warning: this tool contains bugs and may break your data. It is AI-generated, after all. Keep backups of your original activities before replacing anything.
Note: I tested this tool on macOS 15 arm64 only. It may not work correctly on other platforms.
swimfit inspect ACTIVITY.fit
swimfit dump ACTIVITY.fit
swimfit dump --unknowns-only ACTIVITY.fit
swimfit dump --json ACTIVITY.fit
swimfit detect ACTIVITY.fit
swimfit detect --min-length-seconds 10 --max-speed 3 --min-strokes 0 ACTIVITY.fit
swimfit repair --dry-run ACTIVITY.fit
swimfit repair ACTIVITY.fit --output ACTIVITY-fixed.fit
swimfit repair --merge-phantom-time ACTIVITY.fit --output ACTIVITY-fixed.fitinspect,detect, andrepairprint human summaries as terminal tables with one-based length numbers, separateUnitcolumns for metric/value summaries, and parenthesized units in per-record headers.dumpprints every decoded FIT message and field. Use--unknowns-onlyto focus on unresolved/profile-gap fields, Garmin-private messages, developer fields, and invalid sentinels.detectreports impossible active pool lengths, active totals, and anomaly thresholds.repairconverts detected impossible active lengths to idle/rest, validates length/lap indexes, removes stale Garmin best-effort messages, normalizes record distance/speed/cadence fields when present, and updates swim summary fields. Use--dry-runto preview every planned field change without writing a file.--jsonprints structured JSON forinspect,dump,detect, andrepair.- Options use double-dash long form only, for example
--dry-run; single-dash forms are rejected.
For swimming activities with summary distance but no length messages, inspect and detect print a warning. These are often
manual-like entries or minimal exports; per-length detection and repair are not available.
Use repair --merge-phantom-time when Garmin split one real length into several impossible active lengths, but the real total
distance should stay the same. In that mode, contiguous detected phantom lengths donate their time and strokes to the following
same-stroke active length before they are converted to idle/rest. This keeps the corrected distance while avoiding an impossible
best pace from the remaining fragment.
Leave it off when the extra detection came from a real partial swim, a pool exit on the far side, or any case where dropping that active time is intentional. Those activities may correctly repair to a lower total distance.
- Swim Data Analyser notes that best-effort records are currently removed. For a file being corrected, any embedded best effort or personal record is very likely wrong, and deciding whether a time is a personal record is not possible from one FIT file alone.
Supplemental FIT message and field names are generated from Garmin's official Profile.xlsx. To refresh them after downloading a
new FIT SDK profile, run:
just update-fit-profile /path/to/Profile.xlsx
just checkReview the generated internal/fitprofile/generated.go diff before committing.
Tracked FIT fixtures under internal/fitactivity/testdata/ are sanitized copies derived from local exports. To refresh or add one,
use:
go run ./scripts/sanitize-fit-fixture.go --input data/private.fit --output internal/fitactivity/testdata/example.fitKeep original activity exports under ignored local paths such as data/, out/, or docs/data/.
- Swimming Watch Tools was the original inspiration. The archived PHP editor included Garmin FIT SDK based support for split, merge, delete, change-stroke, and pool-size edits, with watch-specific behavior for Garmin Swim, FR910, FR920, Fenix 2, generic Garmin, and TomTom swim files.
- Swim Data Analyser and its source repository are a modern rewrite. Useful ideas include keeping FIT processing local, offering JSON/FIT export, showing interval summaries, best times, per-stroke summaries, heart-rate overlays, and stroke-rate/stroke-count efficiency plots.
- Michael Stapelberg's
Stamp It! All Programs Must Report Their Version
is the model for the
--versionand--moreversiondiagnostics: stamp the build, plumb it through the program, and report it clearly for debugging.