From 0b124a0f060a2b54d52eb476d5b47edca71f4f4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Sun, 8 Feb 2026 15:40:36 +0000 Subject: [PATCH] Garmin: Fix timestamp16 rollover --- .../service/devices/garmin/fit/FitImporter.java | 15 +++++++++++++-- .../garmin/fit/messages/FitMonitoring.java | 15 ++++++++++++++- .../gadgetbridge/util/GBToStringBuilder.java | 2 +- .../devices/garmin/fit/FitImporterTest.java | 4 +++- 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitImporter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitImporter.java index 98c92123c6..05c3777cd5 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitImporter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitImporter.java @@ -118,6 +118,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB; public class FitImporter { private static final Logger LOG = LoggerFactory.getLogger(FitImporter.class); + private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.ROOT); private final Context context; private final GBDevice gbDevice; @@ -156,6 +157,8 @@ public class FitImporter { * @noinspection StatementWithEmptyBody */ public void importFile(final File file) throws IOException, FitParseException { + LOG.debug("Parsing {}", file.getAbsolutePath()); + reset(); final FitFile fitFile = FitFile.parseIncoming(file); @@ -244,8 +247,12 @@ public class FitImporter { sample.setEndTimestamp(nap.getEndTimestamp() * 1000L); napSamples.add(sample); } else if (record instanceof FitMonitoring monitoringRecord) { - LOG.trace("Monitoring at {}: {}", ts, record); final Long currentMonitoringTimestamp = monitoringRecord.computeTimestamp(lastMonitoringTimestamp); + LOG.trace( + "Monitoring at {}: {}", + SDF.format(new Date(currentMonitoringTimestamp * 1000L)), + record + ); if (!activitySamplesPerTimestamp.containsKey(currentMonitoringTimestamp)) { activitySamplesPerTimestamp.put(currentMonitoringTimestamp, new ArrayList<>()); } @@ -591,7 +598,11 @@ public class FitImporter { for (final long ts : activitySamplesPerTimestamp.keySet()) { if (prevTs > 0 && ts - prevTs > 60) { // Fill gaps between samples - LOG.debug("Filling gap between {} and {}", prevTs, ts); + LOG.debug( + "Filling gap between {} and {}", + SDF.format(new Date(prevTs * 1000L)), + SDF.format(new Date(ts * 1000L)) + ); for (int i = prevTs + 60; i < ts; i += 60) { final GarminActivitySample sample = new GarminActivitySample(); sample.setTimestamp(i); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMonitoring.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMonitoring.java index 579ae8bccf..378c22553a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMonitoring.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMonitoring.java @@ -358,7 +358,20 @@ public class FitMonitoring extends RecordData { if (timestamp16 != null && lastMonitoringTimestamp != null) { final int referenceGarminTs = GarminTimeUtils.unixTimeToGarminTimestamp(lastMonitoringTimestamp.intValue()); - return (long) (lastMonitoringTimestamp.intValue() + ((timestamp16 - (referenceGarminTs & 0xffff)) & 0xffff)); + int timeDiff = (timestamp16 & 0xFFFF) - (referenceGarminTs & 0xFFFF); + + // Handle rollover + if (timeDiff < -32768) { + timeDiff += 65536; + } else if (timeDiff > 32768) { + timeDiff -= 65536; + } + + return lastMonitoringTimestamp + timeDiff; + } + + if (lastMonitoringTimestamp != null) { + return lastMonitoringTimestamp; } return getComputedTimestamp(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBToStringBuilder.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBToStringBuilder.java index cce70c3367..59490e04e4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBToStringBuilder.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBToStringBuilder.java @@ -10,7 +10,7 @@ import java.util.Locale; public class GBToStringBuilder extends ToStringBuilder { public static final GBToStringStyle STYLE = new GBToStringStyle(); - private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US); + private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.ROOT); public GBToStringBuilder(final Object object) { super(object, STYLE); diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitImporterTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitImporterTest.java index 89f4432cca..6d2db6ed78 100644 --- a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitImporterTest.java +++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitImporterTest.java @@ -5,7 +5,9 @@ import org.junit.Test; import java.io.File; -public class FitImporterTest { +import nodomain.freeyourgadget.gadgetbridge.test.TestBase; + +public class FitImporterTest extends TestBase { @Test @Ignore("helper test for development, remove this while debugging") public void localTest() throws Exception {