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 { parseFitFile } from '../fitParser.js';
describe('FIT Parser Migration', () => {
it('should parse sample file correctly', async () => {
const buffer = await loadTestFile('sample.fit');
const result = parseFitFile(buffer);
expect(result.records).toBeDefined();
expect(result.records.length).toBeGreaterThan(0);
});
it('should have correct field names', async () => {
const buffer = await loadTestFile('sample.fit');
const result = parseFitFile(buffer);
const record = result.records[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 = parseFitFile(buffer);
const record = result.records[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