Skip to main content

FIT Parser Migration Guide

Guide for updating or migrating the FIT file parser implementation.

Current Implementation​

FitFileViewer uses the Garmin FIT SDK for JavaScript:

import { Decoder, Stream } from "@garmin/fitsdk";

function parseFitFile(arrayBuffer) {
const stream = new Stream(arrayBuffer);
const decoder = new Decoder(stream);

// Decode all messages
const { messages } = decoder.read();

return {
records: messages.recordMesgs,
laps: messages.lapMesgs,
sessions: messages.sessionMesgs,
events: messages.eventMesgs,
};
}

Migration Considerations​

When to Migrate​

Consider migration when:

  • New SDK version with breaking changes
  • Performance improvements needed
  • Bug fixes required
  • New message types supported

SDK Version Update​

# Check current version
npm list @garmin/fitsdk

# Update to latest
npm update @garmin/fitsdk

# Or specific version
npm install @garmin/fitsdk@21.x.x

Data Structure Changes​

Record Fields​

FIT SDK may add/change field names:

// Map old field names to new
const fieldMapping = {
// Old SDK New SDK
positionLat: "position_lat",
positionLong: "position_long",
enhancedSpeed: "enhanced_speed",
};

function normalizeRecord(record) {
const normalized = {};
for (const [key, value] of Object.entries(record)) {
const newKey = fieldMapping[key] || key;
normalized[newKey] = value;
}
return normalized;
}

Timestamp Handling​

// FIT timestamps are seconds from Dec 31, 1989
const FIT_EPOCH = new Date("1989-12-31T00:00:00Z").getTime();

function fitTimestampToDate(fitTimestamp) {
return new Date(FIT_EPOCH + fitTimestamp * 1000);
}

Coordinate Conversion​

// FIT uses semicircles, need to convert to degrees
const SEMICIRCLES_TO_DEGREES = 180 / Math.pow(2, 31);

function semicirclesToDegrees(semicircles) {
return semicircles * SEMICIRCLES_TO_DEGREES;
}

Testing Migration​

Test Suite​

import { describe, it, expect } from "vitest";
import { decodeFitFile } from "../fitParser.js";

describe("FIT Parser Migration", () => {
it("should parse sample file correctly", async () => {
const buffer = await loadTestFile("sample.fit");
const result = await decodeFitFile(buffer);

expect(result.messages.recordMesgs).toBeDefined();
expect(result.messages.recordMesgs.length).toBeGreaterThan(0);
});

it("should have correct field names", async () => {
const buffer = await loadTestFile("sample.fit");
const result = await decodeFitFile(buffer);
const record = result.messages.recordMesgs[0];

expect(record.position_lat).toBeDefined();
expect(record.position_long).toBeDefined();
expect(record.timestamp).toBeDefined();
});

it("should convert coordinates correctly", async () => {
const buffer = await loadTestFile("sample.fit");
const result = await decodeFitFile(buffer);
const record = result.messages.recordMesgs[0];

// Latitude should be in degrees (-90 to 90)
expect(record.position_lat).toBeGreaterThan(-90);
expect(record.position_lat).toBeLessThan(90);
});
});

Sample Files​

Use test files from:

fit-test-files/
β”œβ”€β”€ _Fenton_Michigan_*.fit
β”œβ”€β”€ Virtual_Zwift_*.fit
└── ...

Backwards Compatibility​

Version Detection​

function detectSdkVersion() {
// Check for new features/fields
try {
const decoder = new Decoder();
if (typeof decoder.read === "function") {
return "21.x";
}
} catch {
return "unknown";
}
}

Graceful Fallback​

function getRecords(messages) {
// Try new format first
if (messages.recordMesgs) {
return messages.recordMesgs;
}

// Fall back to old format
if (messages.records) {
return messages.records;
}

return [];
}

Resources​


Related: Architecture Overview