diff --git a/src/androidTest/java/de/dennisguse/opentracks/io/file/exporter/KmlTrackExporterTest.java b/src/androidTest/java/de/dennisguse/opentracks/io/file/exporter/KmlTrackExporterTest.java
index 4cfe9fcbe..ba4533995 100644
--- a/src/androidTest/java/de/dennisguse/opentracks/io/file/exporter/KmlTrackExporterTest.java
+++ b/src/androidTest/java/de/dennisguse/opentracks/io/file/exporter/KmlTrackExporterTest.java
@@ -1,5 +1,7 @@
package de.dennisguse.opentracks.io.file.exporter;
+import static org.junit.Assert.assertEquals;
+
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
@@ -10,12 +12,11 @@ import org.junit.runners.JUnit4;
import java.io.ByteArrayOutputStream;
import java.time.Instant;
+import java.time.ZoneOffset;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
-import static org.junit.Assert.assertEquals;
-
@RunWith(JUnit4.class)
public class KmlTrackExporterTest {
@@ -27,6 +28,8 @@ public class KmlTrackExporterTest {
@Test
public void writeCloseSegment_only_write_sensordata_if_present() {
String expected = "1970-01-01T00:00:00Z\n" +
+ "\n" +
+ "1970-01-01T01:00:00+01:00\n" +
"\n" +
"\n" +
"\n" +
@@ -41,7 +44,8 @@ public class KmlTrackExporterTest {
KMLTrackExporter kmlTrackWriter = (KMLTrackExporter) TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context);
kmlTrackWriter.prepare(outputStream);
- kmlTrackWriter.writeTrackPoint(trackPoint);
+ kmlTrackWriter.writeTrackPoint(ZoneOffset.UTC, trackPoint);
+ kmlTrackWriter.writeTrackPoint(ZoneOffset.ofTotalSeconds(3600), trackPoint);
// when
kmlTrackWriter.writeCloseSegment();
diff --git a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java
index fb681c958..d8e9be3ee 100644
--- a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java
+++ b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java
@@ -219,6 +219,7 @@ public class ExportImportTest {
TrackStatistics importedTrackStatistics = importedTrack.getTrackStatistics();
// Time
+ assertEquals(track.getZoneOffset(), importedTrack.getZoneOffset());
assertEquals(Instant.parse("2020-02-02T02:02:02Z"), importedTrackStatistics.getStartTime());
assertEquals(Instant.parse("2020-02-02T02:02:24Z"), importedTrackStatistics.getStopTime());
@@ -338,6 +339,7 @@ public class ExportImportTest {
TrackStatistics importedTrackStatistics = importedTrack.getTrackStatistics();
// Time
+ assertEquals(track.getZoneOffset(), importedTrack.getZoneOffset());
assertEquals(Instant.parse("2020-02-02T02:02:03Z"), importedTrackStatistics.getStartTime());
assertEquals(Instant.parse("2020-02-02T02:02:23Z"), importedTrackStatistics.getStopTime());
diff --git a/src/androidTest/java/de/dennisguse/opentracks/util/StringUtilsTest.java b/src/androidTest/java/de/dennisguse/opentracks/util/StringUtilsTest.java
index e71ab2513..9505ab129 100644
--- a/src/androidTest/java/de/dennisguse/opentracks/util/StringUtilsTest.java
+++ b/src/androidTest/java/de/dennisguse/opentracks/util/StringUtilsTest.java
@@ -16,6 +16,9 @@
package de.dennisguse.opentracks.util;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
@@ -31,9 +34,6 @@ import java.util.TimeZone;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Speed;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
/**
* Tests for {@link StringUtils}.
*
@@ -141,7 +141,7 @@ public class StringUtilsTest {
// This comparision tends to be flaky (difference of 1ms)
// Assert.assertEquals(calendar.getTimeInMillis(), StringUtils.parseTime(xmlDateTime));
- assertTrue(calendar.getTimeInMillis() + " vs. " + StringUtils.parseTime(xmlDateTime), Math.abs(calendar.getTimeInMillis() - StringUtils.parseTime(xmlDateTime).toEpochMilli()) <= 1);
+ assertTrue(calendar.getTimeInMillis() + " vs. " + StringUtils.parseTime(xmlDateTime), Math.abs(calendar.getTimeInMillis() - StringUtils.parseTime(xmlDateTime).toInstant().toEpochMilli()) <= 1);
}
@Test
diff --git a/src/main/java/de/dennisguse/opentracks/content/data/Track.java b/src/main/java/de/dennisguse/opentracks/content/data/Track.java
index f17506f95..b8d96f06d 100644
--- a/src/main/java/de/dennisguse/opentracks/content/data/Track.java
+++ b/src/main/java/de/dennisguse/opentracks/content/data/Track.java
@@ -21,7 +21,10 @@ import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
import java.util.Objects;
import java.util.UUID;
@@ -45,8 +48,19 @@ public class Track {
private String icon = "";
+ private final ZoneOffset zoneOffset;
+
private TrackStatistics trackStatistics = new TrackStatistics();
+ @VisibleForTesting
+ public Track() {
+ this.zoneOffset = ZoneOffset.UTC;
+ }
+
+ public Track(@NonNull ZoneOffset zoneOffset) {
+ this.zoneOffset = zoneOffset;
+ }
+
/**
* May be null if the track was not loaded from the database.
*/
@@ -99,6 +113,14 @@ public class Track {
this.icon = icon;
}
+ public ZoneOffset getZoneOffset() {
+ return zoneOffset;
+ }
+
+ public OffsetDateTime getStartTime() {
+ return trackStatistics.getStartTime().atOffset(zoneOffset);
+ }
+
@NonNull
public TrackStatistics getTrackStatistics() {
return trackStatistics;
diff --git a/src/main/java/de/dennisguse/opentracks/content/data/TracksColumns.java b/src/main/java/de/dennisguse/opentracks/content/data/TracksColumns.java
index a705a0b4c..16d1f669d 100644
--- a/src/main/java/de/dennisguse/opentracks/content/data/TracksColumns.java
+++ b/src/main/java/de/dennisguse/opentracks/content/data/TracksColumns.java
@@ -41,6 +41,7 @@ public interface TracksColumns extends BaseColumns {
String DESCRIPTION = "description"; // track description
String CATEGORY = "category"; // track activity type
String STARTTIME = "starttime"; // track start time
+ String STARTTIME_OFFSET = "starttime_offset"; // in plus/minus in seconds
String STOPTIME = "stoptime"; // track stop time
String MARKER_COUNT = "markerCount"; // the numbers of markers (virtual column)
@Deprecated
@@ -77,7 +78,8 @@ public interface TracksColumns extends BaseColumns {
+ ALTITUDE_GAIN + " FLOAT, "
+ ICON + " TEXT, "
+ UUID + " BLOB, "
- + ALTITUDE_LOSS + " FLOAT)";
+ + ALTITUDE_LOSS + " FLOAT, "
+ + STARTTIME_OFFSET + " INTEGER)";
String CREATE_TABLE_INDEX = "CREATE UNIQUE INDEX " + TABLE_NAME + "_" + UUID + "_index ON " + TABLE_NAME + "(" + UUID + ")";
diff --git a/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java b/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java
index ad090fff8..3673cd06d 100644
--- a/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java
+++ b/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java
@@ -32,6 +32,7 @@ import androidx.annotation.VisibleForTesting;
import java.io.File;
import java.time.Duration;
import java.time.Instant;
+import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
@@ -98,6 +99,7 @@ public class ContentProviderUtils {
int descriptionIndex = cursor.getColumnIndexOrThrow(TracksColumns.DESCRIPTION);
int categoryIndex = cursor.getColumnIndexOrThrow(TracksColumns.CATEGORY);
int startTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.STARTTIME);
+ int startTimeOffsetIndex = cursor.getColumnIndexOrThrow(TracksColumns.STARTTIME_OFFSET);
int stopTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.STOPTIME);
int totalDistanceIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALDISTANCE);
int totalTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALTIME);
@@ -109,7 +111,7 @@ public class ContentProviderUtils {
int altitudeLossIndex = cursor.getColumnIndexOrThrow(TracksColumns.ALTITUDE_LOSS);
int iconIndex = cursor.getColumnIndexOrThrow(TracksColumns.ICON);
- Track track = new Track();
+ Track track = new Track(ZoneOffset.ofTotalSeconds(cursor.getInt(startTimeOffsetIndex)));
TrackStatistics trackStatistics = track.getTrackStatistics();
if (!cursor.isNull(idIndex)) {
track.setId(new Track.Id(cursor.getLong(idIndex)));
@@ -126,6 +128,7 @@ public class ContentProviderUtils {
if (!cursor.isNull(categoryIndex)) {
track.setCategory(cursor.getString(categoryIndex));
}
+
if (!cursor.isNull(startTimeIndex)) {
trackStatistics.setStartTime(Instant.ofEpochMilli(cursor.getLong(startTimeIndex)));
}
@@ -288,6 +291,7 @@ public class ContentProviderUtils {
values.put(TracksColumns.NAME, track.getName());
values.put(TracksColumns.DESCRIPTION, track.getDescription());
values.put(TracksColumns.CATEGORY, track.getCategory());
+ values.put(TracksColumns.STARTTIME_OFFSET, track.getZoneOffset().getTotalSeconds());
if (trackStatistics.getStartTime() != null) {
values.put(TracksColumns.STARTTIME, trackStatistics.getStartTime().toEpochMilli());
}
diff --git a/src/main/java/de/dennisguse/opentracks/content/provider/CustomSQLiteOpenHelper.java b/src/main/java/de/dennisguse/opentracks/content/provider/CustomSQLiteOpenHelper.java
index 4377725c1..0df47af4f 100644
--- a/src/main/java/de/dennisguse/opentracks/content/provider/CustomSQLiteOpenHelper.java
+++ b/src/main/java/de/dennisguse/opentracks/content/provider/CustomSQLiteOpenHelper.java
@@ -9,6 +9,9 @@ import android.util.Log;
import androidx.annotation.VisibleForTesting;
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.zone.ZoneRules;
import java.util.UUID;
import de.dennisguse.opentracks.Startup;
@@ -26,7 +29,7 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName();
- private static final int DATABASE_VERSION = 32;
+ private static final int DATABASE_VERSION = 33;
public CustomSQLiteOpenHelper(Context context) {
this(context, ((Startup) context.getApplicationContext()).getDatabaseName());
@@ -86,6 +89,9 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
case 32:
upgradeFrom31to32(db);
break;
+ case 33:
+ upgradeFrom32to33(db);
+ break;
default:
throw new RuntimeException("Not implemented: upgrade to " + toVersion);
@@ -125,6 +131,9 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
case 31:
downgradeFrom32to31(db);
break;
+ case 32:
+ downgradeFrom33to32(db);
+ break;
default:
throw new RuntimeException("Not implemented: downgrade to " + toVersion);
}
@@ -459,6 +468,52 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
db.execSQL("CREATE INDEX trackpoints_trackid_index ON trackpoints(trackid)");
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ }
+
+ /**
+ * Add timezone to Track.
+ */
+ private void upgradeFrom32to33(SQLiteDatabase db) {
+ db.beginTransaction();
+
+ db.execSQL("ALTER TABLE tracks ADD COLUMN starttime_offset INTEGER");
+
+ ZoneRules zoneRules = ZoneOffset.systemDefault().getRules();
+
+ try (Cursor cursor = db.query("tracks", new String[]{"_id", "starttime"}, null, null, null, null, null)) {
+ if (cursor.moveToFirst()) {
+ int trackIdIndex = cursor.getColumnIndexOrThrow("_id");
+ int startTimeIndex = cursor.getColumnIndexOrThrow("starttime");
+ do {
+ Track.Id trackId = new Track.Id(cursor.getLong(trackIdIndex));
+ long startTime = cursor.getLong(startTimeIndex);
+
+ ContentValues cv = new ContentValues();
+ cv.put("starttime_offset", zoneRules.getOffset(Instant.ofEpochMilli(startTime)).getTotalSeconds());
+ db.update("tracks", cv, "_id = ?", new String[]{String.valueOf(trackId.getId())});
+ } while (cursor.moveToNext());
+ }
+ }
+
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ }
+
+ private void downgradeFrom33to32(SQLiteDatabase db) {
+ db.beginTransaction();
+
+ db.execSQL("DROP INDEX tracks_uuid_index");
+
+ db.execSQL("ALTER TABLE tracks RENAME TO tracks_old");
+ db.execSQL("CREATE TABLE tracks (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, starttime INTEGER, stoptime INTEGER, numpoints INTEGER, totaldistance FLOAT, totaltime INTEGER, movingtime INTEGER, avgspeed FLOAT, avgmovingspeed FLOAT, maxspeed FLOAT, minelevation FLOAT, maxelevation FLOAT, elevationgain FLOAT, icon TEXT, uuid BLOB, elevationloss FLOAT)");
+ db.execSQL("INSERT INTO tracks SELECT _id, name, description, category, starttime, stoptime, numpoints, totaldistance, totaltime, movingtime, avgspeed, avgmovingspeed, maxspeed, minelevation, maxelevation, elevationgain, icon, uuid, elevationloss FROM tracks_old");
+ db.execSQL("DROP TABLE tracks_old");
+
+ db.execSQL("CREATE UNIQUE INDEX tracks_uuid_index ON tracks(uuid)");
+
+
db.setTransactionSuccessful();
db.endTransaction();
}
diff --git a/src/main/java/de/dennisguse/opentracks/io/file/exporter/GPXTrackExporter.java b/src/main/java/de/dennisguse/opentracks/io/file/exporter/GPXTrackExporter.java
index 7a97d170b..e5406403f 100644
--- a/src/main/java/de/dennisguse/opentracks/io/file/exporter/GPXTrackExporter.java
+++ b/src/main/java/de/dennisguse/opentracks/io/file/exporter/GPXTrackExporter.java
@@ -24,6 +24,7 @@ import androidx.annotation.NonNull;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.text.NumberFormat;
+import java.time.ZoneOffset;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
@@ -158,7 +159,7 @@ public class GPXTrackExporter implements TrackExporter {
writeOpenSegment();
wroteSegment = true;
- writeTrackPoint(trackPoint, sensorPoints);
+ writeTrackPoint(track.getZoneOffset(), trackPoint, sensorPoints);
sensorPoints.clear();
break;
case SENSORPOINT:
@@ -171,7 +172,7 @@ public class GPXTrackExporter implements TrackExporter {
wroteSegment = true;
}
- writeTrackPoint(trackPoint, sensorPoints);
+ writeTrackPoint(track.getZoneOffset(), trackPoint, sensorPoints);
sensorPoints.clear();
break;
default:
@@ -230,6 +231,7 @@ public class GPXTrackExporter implements TrackExporter {
+ " http://www.topografix.com/GPX/Private/TopoGrafix/0/1 http://www.topografix.com/GPX/Private/TopoGrafix/0/1/topografix.xsd"
+ " http://www.garmin.com/xmlschemas/TrackPointExtension/v2 https://www8.garmin.com/xmlschemas/TrackPointExtensionv2.xsd"
+ " http://www.garmin.com/xmlschemas/PowerExtension/v1 https://www8.garmin.com/xmlschemas/PowerExtensionv1.xsd"
+ + " http://www.garmin.com/xmlschemas/TrackStatsExtension/v1"
+ " http://opentracksapp.com/xmlschemas/v1 http://opentracksapp.com/xmlschemas/OpenTracks_v1.xsd\">");
}
}
@@ -248,7 +250,7 @@ public class GPXTrackExporter implements TrackExporter {
throw new InterruptedException();
}
Marker marker = contentProviderUtils.createMarker(cursor);
- writeMarker(marker);
+ writeMarker(track.getZoneOffset(), marker);
cursor.moveToNext();
}
@@ -256,13 +258,13 @@ public class GPXTrackExporter implements TrackExporter {
}
}
- public void writeMarker(Marker marker) {
+ public void writeMarker(ZoneOffset zoneOffset, Marker marker) {
if (printWriter != null) {
printWriter.println("");
if (marker.hasAltitude()) {
printWriter.println("" + ALTITUDE_FORMAT.format(marker.getAltitude().toM()) + "");
}
- printWriter.println("");
+ printWriter.println("");
printWriter.println("" + StringUtils.formatCData(marker.getName()) + "");
printWriter.println("" + StringUtils.formatCData(marker.getDescription()) + "");
printWriter.println("" + StringUtils.formatCData(marker.getCategory()) + "");
@@ -310,7 +312,7 @@ public class GPXTrackExporter implements TrackExporter {
printWriter.println("");
}
- public void writeTrackPoint(TrackPoint trackPoint, List sensorPoints) {
+ public void writeTrackPoint(ZoneOffset zoneOffset, TrackPoint trackPoint, List sensorPoints) {
if (printWriter != null) {
printWriter.println("");
@@ -319,7 +321,7 @@ public class GPXTrackExporter implements TrackExporter {
printWriter.println("" + ALTITUDE_FORMAT.format(trackPoint.getAltitude().toM()) + "");
}
- printWriter.println("");
+ printWriter.println("");
{
String trackPointExtensionContent = "";
diff --git a/src/main/java/de/dennisguse/opentracks/io/file/exporter/KMLTrackExporter.java b/src/main/java/de/dennisguse/opentracks/io/file/exporter/KMLTrackExporter.java
index 59e342bb2..fc6f011e1 100644
--- a/src/main/java/de/dennisguse/opentracks/io/file/exporter/KMLTrackExporter.java
+++ b/src/main/java/de/dennisguse/opentracks/io/file/exporter/KMLTrackExporter.java
@@ -27,8 +27,8 @@ import androidx.annotation.VisibleForTesting;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.text.NumberFormat;
-import java.time.Duration;
import java.time.Instant;
+import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@@ -110,10 +110,7 @@ public class KMLTrackExporter implements TrackExporter {
if (hasMultipleTracks) {
writeMultiTrackBegin();
}
- //TODO Why use startTime of first track for the others?
- Instant startTime = tracks[0].getTrackStatistics().getStartTime();
for (Track track : tracks) {
- Duration offset = Duration.between(track.getTrackStatistics().getStartTime(), startTime);
writeLocations(track);
}
if (hasMultipleTracks) {
@@ -142,7 +139,7 @@ public class KMLTrackExporter implements TrackExporter {
hasMarkers = true;
}
Marker marker = contentProviderUtils.createMarker(cursor);
- writeMarker(marker);
+ writeMarker(marker, track.getZoneOffset());
cursor.moveToNext();
}
@@ -172,12 +169,12 @@ public class KMLTrackExporter implements TrackExporter {
case SEGMENT_START_AUTOMATIC:
if (wroteSegment) writeCloseSegment();
writeOpenSegment();
- writeTrackPoint(trackPoint);
+ writeTrackPoint(track.getZoneOffset(), trackPoint);
wroteSegment = true;
break;
case SEGMENT_END_MANUAL:
if (!wroteSegment) writeOpenSegment();
- writeTrackPoint(trackPoint);
+ writeTrackPoint(track.getZoneOffset(), trackPoint);
writeCloseSegment();
wroteSegment = false;
break;
@@ -188,7 +185,7 @@ public class KMLTrackExporter implements TrackExporter {
writeOpenSegment();
wroteSegment = true;
}
- writeTrackPoint(trackPoint);
+ writeTrackPoint(track.getZoneOffset(), trackPoint);
break;
default:
throw new RuntimeException("Exporting this TrackPoint type is not implemented: " + trackPoint.getType());
@@ -268,14 +265,14 @@ public class KMLTrackExporter implements TrackExporter {
}
}
- private void writeMarker(Marker marker) {
+ private void writeMarker(Marker marker, ZoneOffset zoneOffset) {
if (printWriter != null) {
boolean existsPhoto = FileUtils.buildInternalPhotoFile(context, marker.getTrackId(), marker.getPhotoURI()) != null;
if (marker.hasPhoto() && exportPhotos && existsPhoto) {
float heading = getHeading(marker.getTrackId(), marker.getLocation());
- writePhotoOverlay(marker, heading);
+ writePhotoOverlay(marker, heading, zoneOffset);
} else {
- writePlacemark(marker.getName(), marker.getCategory(), marker.getDescription(), marker.getLocation());
+ writePlacemark(marker.getName(), marker.getCategory(), marker.getDescription(), marker.getLocation(), zoneOffset);
}
}
}
@@ -372,12 +369,12 @@ public class KMLTrackExporter implements TrackExporter {
}
@VisibleForTesting
- void writeTrackPoint(TrackPoint trackPoint) {
+ void writeTrackPoint(ZoneOffset zoneOffset, TrackPoint trackPoint) {
if (printWriter != null) {
- printWriter.println("" + getTime(trackPoint.getLocation()) + "");
+ printWriter.println("" + getTime(zoneOffset, trackPoint.getLocation()) + "");
if (trackPoint.hasLocation()) {
- printWriter.println("" + (trackPoint.hasLocation() ? getCoordinates(trackPoint.getLocation(), " ") : "") + "");
+ printWriter.println("" + getCoordinates(trackPoint.getLocation(), " ") + "");
} else {
printWriter.println("");
}
@@ -420,12 +417,12 @@ public class KMLTrackExporter implements TrackExporter {
* @param description the description
* @param location the location
*/
- private void writePlacemark(String name, String category, String description, Location location) {
+ private void writePlacemark(String name, String category, String description, Location location, ZoneOffset zoneOffset) {
if (location != null) {
printWriter.println("");
printWriter.println("" + StringUtils.formatCData(name) + "");
printWriter.println("" + StringUtils.formatCData(description) + "");
- printWriter.println("" + getTime(location) + "");
+ printWriter.println("" + getTime(zoneOffset, location) + "");
printWriter.println("#" + KMLTrackExporter.MARKER_STYLE + "");
writeCategory(category);
printWriter.println("");
@@ -435,7 +432,7 @@ public class KMLTrackExporter implements TrackExporter {
}
}
- private void writePhotoOverlay(Marker marker, float heading) {
+ private void writePhotoOverlay(Marker marker, float heading, ZoneOffset zoneOffset) {
printWriter.println("");
printWriter.println("" + StringUtils.formatCData(marker.getName()) + "");
printWriter.println("" + StringUtils.formatCData(marker.getDescription()) + "");
@@ -446,7 +443,7 @@ public class KMLTrackExporter implements TrackExporter {
printWriter.print("" + heading + "");
printWriter.print("90");
printWriter.println("");
- printWriter.println("" + getTime(marker.getLocation()) + "");
+ printWriter.println("" + getTime(zoneOffset, marker.getLocation()) + "");
printWriter.println("#" + MARKER_STYLE + "");
writeCategory(marker.getCategory());
@@ -472,8 +469,8 @@ public class KMLTrackExporter implements TrackExporter {
*
* @param location the location
*/
- private String getTime(Location location) {
- return StringUtils.formatDateTimeIso8601(Instant.ofEpochMilli(location.getTime()));
+ private String getTime(ZoneOffset zoneOffset, Location location) {
+ return StringUtils.formatDateTimeIso8601(Instant.ofEpochMilli(location.getTime()), zoneOffset);
}
/**
diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/GpxTrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/GpxTrackImporter.java
index 71f1b395c..9f29485df 100644
--- a/src/main/java/de/dennisguse/opentracks/io/file/importer/GpxTrackImporter.java
+++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/GpxTrackImporter.java
@@ -23,7 +23,8 @@ import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.helpers.DefaultHandler;
-import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
@@ -86,6 +87,8 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac
private final Context context;
+ private ZoneOffset zoneOffset;
+
// Belongs to the current track
private final ArrayList markers = new ArrayList<>();
@@ -157,7 +160,8 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac
onMarkerEnd();
break;
case TAG_TRACK:
- trackImporter.setTrack(context, name, uuid, description, category, null);
+ trackImporter.setTrack(context, name, uuid, description, category, null, zoneOffset);
+ zoneOffset = null;
break;
case TAG_TRACK_SEGMENT:
onTrackSegmentEnd();
@@ -256,14 +260,17 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac
private TrackPoint createTrackPoint() throws ParsingException {
- Instant parsedTime;
+ OffsetDateTime parsedTime;
try {
parsedTime = StringUtils.parseTime(time);
+ if (zoneOffset == null) {
+ zoneOffset = parsedTime.getOffset();
+ }
} catch (Exception e) {
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", time)), e);
}
- TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, parsedTime);
+ TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, parsedTime.toInstant());
if (latitude == null || longitude == null) {
return trackPoint;
}
diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/KmlTrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/KmlTrackImporter.java
index 0a7cdf8a2..9b1f2cbd9 100644
--- a/src/main/java/de/dennisguse/opentracks/io/file/importer/KmlTrackImporter.java
+++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/KmlTrackImporter.java
@@ -26,6 +26,8 @@ import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@@ -74,6 +76,8 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
private final Context context;
// Belongs to the current track
+ private ZoneOffset zoneOffset;
+
private final ArrayList whenList = new ArrayList<>();
private final ArrayList locationList = new ArrayList<>();
@@ -157,7 +161,8 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
onMarkerLocationEnd();
break;
case TAG_GX_MULTI_TRACK:
- trackImporter.setTrack(context, name, uuid, description, category, icon);
+ trackImporter.setTrack(context, name, uuid, description, category, icon, zoneOffset);
+ zoneOffset = null;
break;
case TAG_GX_TRACK:
onTrackSegmentEnd();
@@ -195,7 +200,15 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
break;
case TAG_WHEN:
if (content != null) {
- whenList.add(StringUtils.parseTime(content.trim()));
+ try {
+ OffsetDateTime time = StringUtils.parseTime(content.trim());
+ if (zoneOffset == null) {
+ zoneOffset = time.getOffset();
+ }
+ whenList.add(time.toInstant());
+ } catch (Exception e) {
+ throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", content.trim())), e);
+ }
}
break;
diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/ParsingException.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/ParsingException.java
index 0c3255c02..59de5b072 100644
--- a/src/main/java/de/dennisguse/opentracks/io/file/importer/ParsingException.java
+++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/ParsingException.java
@@ -4,6 +4,10 @@ import androidx.annotation.NonNull;
public class ParsingException extends RuntimeException {
+ protected ParsingException(@NonNull String message) {
+ super(message);
+ }
+
protected ParsingException(@NonNull String message, Exception cause) {
super(message, cause);
}
diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/TrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/TrackImporter.java
index 930252f57..31e67dd5f 100644
--- a/src/main/java/de/dennisguse/opentracks/io/file/importer/TrackImporter.java
+++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/TrackImporter.java
@@ -5,10 +5,12 @@ import android.net.Uri;
import android.util.Log;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import java.io.File;
import java.time.Duration;
import java.time.Instant;
+import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
@@ -89,8 +91,8 @@ public class TrackImporter {
this.markers.addAll(markers);
}
- void setTrack(Context context, String name, String uuid, String description, String category, String icon) {
- track = new Track();
+ void setTrack(Context context, String name, String uuid, String description, String category, String icon, @Nullable ZoneOffset zoneOffset) {
+ track = new Track(zoneOffset != null ? zoneOffset : ZoneOffset.UTC);
track.setName(name != null ? name : "");
try {
diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java
index 45e42df5d..69e3b1d65 100644
--- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java
+++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java
@@ -9,6 +9,8 @@ import android.util.Pair;
import androidx.annotation.NonNull;
+import java.time.ZoneOffset;
+
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Marker;
@@ -47,9 +49,9 @@ class TrackRecordingManager {
contentProviderUtils = new ContentProviderUtils(context);
}
- Track.Id start(TrackPoint segmentStartTrackPoint) {
+ Track.Id start(TrackPoint segmentStartTrackPoint, ZoneOffset zoneOffset) {
// Create new track
- Track track = new Track();
+ Track track = new Track(zoneOffset);
trackId = contentProviderUtils.insertTrack(track);
track.setId(trackId);
diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java
index a23e5e012..7e9e6b133 100644
--- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java
+++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java
@@ -34,6 +34,7 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import java.time.Duration;
+import java.time.ZoneOffset;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackListActivity;
@@ -222,7 +223,9 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
}
// Set recording status
- Track.Id trackId = trackRecordingManager.start(trackPointCreator.createSegmentStartManual());
+ TrackPoint segmentStartManual = trackPointCreator.createSegmentStartManual();
+ ZoneOffset zoneOffset = ZoneOffset.systemDefault().getRules().getOffset(segmentStartManual.getTime());
+ Track.Id trackId = trackRecordingManager.start(segmentStartManual, zoneOffset);
updateRecordingStatus(RecordingStatus.record(trackId));
startRecording();
diff --git a/src/main/java/de/dennisguse/opentracks/util/StringUtils.java b/src/main/java/de/dennisguse/opentracks/util/StringUtils.java
index 7990a40a1..30b48caf5 100644
--- a/src/main/java/de/dennisguse/opentracks/util/StringUtils.java
+++ b/src/main/java/de/dennisguse/opentracks/util/StringUtils.java
@@ -30,6 +30,7 @@ import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
+import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
@@ -49,8 +50,6 @@ public class StringUtils {
private static final String TAG = StringUtils.class.getSimpleName();
- private static final String COORDINATE_DEGREE = "\u00B0";
-
private StringUtils() {
}
@@ -70,10 +69,12 @@ public class StringUtils {
}
/**
- * Formats the time using the ISO 8601 date time format with fractional seconds in UTC time zone.
+ * Formats the time using the ISO 8601 date time format with fractional seconds.
*/
- public static String formatDateTimeIso8601(@NonNull Instant time) {
- return time.toString();
+ public static String formatDateTimeIso8601(@NonNull Instant time, ZoneOffset zoneOffset) {
+ return time
+ .atOffset(zoneOffset)
+ .format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
}
/**
@@ -286,14 +287,14 @@ public class StringUtils {
*
* @param xmlDateTime the XML date time string
*/
- public static Instant parseTime(String xmlDateTime) {
+ public static OffsetDateTime parseTime(String xmlDateTime) {
try {
TemporalAccessor t = DateTimeFormatter.ISO_DATE_TIME.parseBest(xmlDateTime, ZonedDateTime::from, LocalDateTime::from);
if (t instanceof LocalDateTime) {
Log.w(TAG, "Date does not contain timezone information: using UTC.");
t = ((LocalDateTime) t).atZone(ZoneOffset.UTC);
}
- return Instant.from(t);
+ return OffsetDateTime.from(t);
} catch (Exception e) {
Log.e(TAG, "Invalid XML dateTime value");
throw e;