From 86d42bab7870e80a7c7f4c28c5784633a2534576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Sat, 23 May 2026 18:42:47 +0100 Subject: [PATCH] GPX: Fix parsing of timestamps without timezone Although these are non-conforming, there are a lot of gpx files online that do not contain a timezone and fail to be parsed. --- .../gadgetbridge/util/gpx/GpxParser.java | 4 +++ .../gadgetbridge/util/gpx/GPXParserTest.java | 25 +++++++++++++ .../resources/gpx-parser-test-datetime.gpx | 35 +++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 app/src/test/resources/gpx-parser-test-datetime.gpx diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/GpxParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/GpxParser.java index 47def6425e..a0e9c5a344 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/GpxParser.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/GpxParser.java @@ -160,6 +160,10 @@ public class GpxParser { if (text == null || text.length() < 1) { return defaultDate; } + // If no timezone indicator is present, assume UTC + if (!text.endsWith("Z") && !text.contains("+") && !(text.length() > 19 && text.charAt(19) == '-')) { + return ISO8601Utils.parse(text + "Z", new ParsePosition(0)); + } return ISO8601Utils.parse(text, new ParsePosition(0)); } diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/GPXParserTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/GPXParserTest.java index 34cb6db086..1c0a763beb 100644 --- a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/GPXParserTest.java +++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/GPXParserTest.java @@ -201,6 +201,31 @@ public class GPXParserTest extends TestBase { Assert.assertEquals(trackpoint.getVdop(), activityPoint.getLocation().getVdop(), 0.0); } + @Test + public void shouldParseDatetimeVariants() throws IOException, GpxParseException { + // Validates parsing of timestamps with no timezone (treated as UTC), explicit UTC (Z), + // positive offset, and negative offset. + try (final InputStream inputStream = getClass().getResourceAsStream("/gpx-parser-test-datetime.gpx")) { + final GpxParser gpxParser = new GpxParser(inputStream); + final GpxFile gpxFile = gpxParser.getGpxFile(); + + // Metadata time: No timezone indicator: should be treated as UTC + Assert.assertEquals(Date.from(Instant.parse("2026-05-23T19:31:00Z")), gpxFile.getTime()); + + final List points = gpxFile.getPoints(); + Assert.assertEquals(4, points.size()); + + // No timezone: treated as UTC + Assert.assertEquals(Date.from(Instant.parse("2026-05-23T19:31:24Z")), points.get(0).getTime()); + // Explicit UTC (Z) + Assert.assertEquals(Date.from(Instant.parse("2026-05-23T19:32:00Z")), points.get(1).getTime()); + // Positive offset (+02:00): 21:33 local = 19:33 UTC + Assert.assertEquals(Date.from(Instant.parse("2026-05-23T19:33:00Z")), points.get(2).getTime()); + // Negative offset (-07:00): 12:34 local = 19:34 UTC + Assert.assertEquals(Date.from(Instant.parse("2026-05-23T19:34:00Z")), points.get(3).getTime()); + } + } + // test import of old GPX v1.0 @Test public void TestGpxImportOld() throws Exception { diff --git a/app/src/test/resources/gpx-parser-test-datetime.gpx b/app/src/test/resources/gpx-parser-test-datetime.gpx new file mode 100644 index 0000000000..bc5c8748a2 --- /dev/null +++ b/app/src/test/resources/gpx-parser-test-datetime.gpx @@ -0,0 +1,35 @@ + + + + Test Timezone Variants + + + + + + + + 440.0 + + + + + 441.0 + + + + + 442.0 + + + + + 443.0 + + + + +