Timezone support: store timezone offset per Track.

When a recording is started the offset of the phone is used.
Timezone is exported to KML and GPX.

Fixes #301.
This commit is contained in:
Dennis Guse
2021-12-07 22:39:40 +01:00
parent 7193171a09
commit 6d04659370
16 changed files with 175 additions and 55 deletions
@@ -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;
@@ -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 + ")";
@@ -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());
}
@@ -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();
}