FIT : Mark exported local/system_timestamp fields as TIMESTAMP

This commit is contained in:
Dany Mestas
2026-07-18 12:42:04 +02:00
parent c258671f35
commit c7aa0307cb
2 changed files with 13 additions and 12 deletions
@@ -5287,6 +5287,7 @@
"5": { "5": {
"num": 5, "num": 5,
"name": "local_timestamp", "name": "local_timestamp",
"type": "TIMESTAMP",
"baseType": "UINT32" "baseType": "UINT32"
}, },
"6": { "6": {
@@ -8364,6 +8365,7 @@
"1": { "1": {
"num": 1, "num": 1,
"name": "system_timestamp", "name": "system_timestamp",
"type": "TIMESTAMP",
"baseType": "UINT32", "baseType": "UINT32",
"UOM": "seconds" "UOM": "seconds"
}, },
@@ -8377,6 +8379,7 @@
"3": { "3": {
"num": 3, "num": 3,
"name": "local_timestamp", "name": "local_timestamp",
"type": "TIMESTAMP",
"baseType": "UINT32", "baseType": "UINT32",
"UOM": "seconds" "UOM": "seconds"
}, },
@@ -43,7 +43,6 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack; import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate; import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.FileType; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.FileType;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitFile; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitFile;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.enums.GarminSport; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.enums.GarminSport;
@@ -563,14 +562,13 @@ public class FitExporter {
} }
private RecordData buildTimestampCorrelation(final long startSeconds, final int utcOffsetSeconds) { private RecordData buildTimestampCorrelation(final long startSeconds, final int utcOffsetSeconds) {
// system_timestamp (field 1) and local_timestamp (field 3) are plain UINT32 — they // All three fields are FIT TIMESTAMP type, so the encoder subtracts the Garmin epoch
// lack the FIT TIMESTAMP marker, so the encoder does NOT auto-subtract the Garmin // automatically — pass raw Unix seconds. system_timestamp and timestamp are the UTC
// epoch; it is applied manually here (same reasoning as buildActivity's field 5). // start instant; local_timestamp carries the phone-zone offset so importers recover
// timestamp (field 253) IS a TIMESTAMP, so it takes raw Unix seconds and the encoder // the timezone as local_timestamp - system_timestamp.
// subtracts the epoch. Importers recover the timezone as local_timestamp - system_timestamp.
return new FitTimestampCorrelation.Builder() return new FitTimestampCorrelation.Builder()
.setSystemTimestamp(startSeconds - GarminTimeUtils.GARMIN_TIME_EPOCH) .setSystemTimestamp(startSeconds)
.setLocalTimestamp((startSeconds + utcOffsetSeconds) - GarminTimeUtils.GARMIN_TIME_EPOCH) .setLocalTimestamp(startSeconds + utcOffsetSeconds)
.setTimestamp(startSeconds) .setTimestamp(startSeconds)
.build(LMT_TIMESTAMP_CORRELATION); .build(LMT_TIMESTAMP_CORRELATION);
} }
@@ -1514,13 +1512,13 @@ public class FitExporter {
final int utcOffsetSeconds) { final int utcOffsetSeconds) {
// NativeFITMessage.ACTIVITY field 0 (total_timer_time) is declared without scale — // NativeFITMessage.ACTIVITY field 0 (total_timer_time) is declared without scale —
// FIT spec is scale=1000 unit=s, so pre-multiply seconds → milliseconds. // FIT spec is scale=1000 unit=s, so pre-multiply seconds → milliseconds.
// Field 5 (local_timestamp) likewise lacks the TIMESTAMP marker, so the encoder // Field 5 (local_timestamp) is a FIT TIMESTAMP, so the encoder subtracts the Garmin
// does not subtract the Garmin epoch — do it manually. Add the phone-zone UTC offset // epoch — pass raw Unix seconds plus the phone-zone UTC offset so local_timestamp
// so local_timestamp holds the local wall-clock; importers recover the timezone as // holds the local wall-clock; importers recover the timezone as
// local_timestamp - timestamp == utcOffsetSeconds (0 when the phone is in UTC). // local_timestamp - timestamp == utcOffsetSeconds (0 when the phone is in UTC).
return new FitActivity.Builder() return new FitActivity.Builder()
.setTimestamp(endSeconds) .setTimestamp(endSeconds)
.setLocalTimestamp((endSeconds + utcOffsetSeconds) - GarminTimeUtils.GARMIN_TIME_EPOCH) .setLocalTimestamp(endSeconds + utcOffsetSeconds)
.setTotalTimerTime(elapsedSeconds * 1000L) .setTotalTimerTime(elapsedSeconds * 1000L)
.setNumSessions(1) .setNumSessions(1)
.setType(ACTIVITY_TYPE_MANUAL) .setType(ACTIVITY_TYPE_MANUAL)