diff --git a/src/androidTest/java/de/dennisguse/opentracks/content/DescriptionGeneratorTest.java b/src/androidTest/java/de/dennisguse/opentracks/content/DescriptionGeneratorTest.java
index b85623873..0655de762 100644
--- a/src/androidTest/java/de/dennisguse/opentracks/content/DescriptionGeneratorTest.java
+++ b/src/androidTest/java/de/dennisguse/opentracks/content/DescriptionGeneratorTest.java
@@ -27,6 +27,8 @@ import org.junit.runner.RunWith;
import java.time.Duration;
import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Distance;
@@ -91,7 +93,7 @@ public class DescriptionGeneratorTest {
+ "Min elevation: -500 m (-1640 ft)
"
+ "Elevation gain: 6000 m (19685 ft)
"
+ "Elevation loss: 6000 m (19685 ft)
"
- + "Recorded: " + StringUtils.formatDateTime(context, START_TIME) + "
";
+ + "Recorded: " + StringUtils.formatDateTime(OffsetDateTime.ofInstant(START_TIME, ZoneId.systemDefault())) + "
";
assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true));
}
@@ -127,7 +129,7 @@ public class DescriptionGeneratorTest {
+ "Fastest pace: 0:10 min/km (0:16 min/mi)
"
+ "Elevation gain: 6000 m (19685 ft)
"
+ "Elevation loss: 6000 m (19685 ft)
"
- + "Recorded: " + StringUtils.formatDateTime(context, START_TIME) + "
";
+ + "Recorded: " + StringUtils.formatDateTime(OffsetDateTime.ofInstant(START_TIME, ZoneId.systemDefault())) + "
";
assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true));
}
diff --git a/src/androidTest/java/de/dennisguse/opentracks/util/StringUtilsTest.java b/src/androidTest/java/de/dennisguse/opentracks/util/StringUtilsTest.java
index 9505ab129..e8255aa89 100644
--- a/src/androidTest/java/de/dennisguse/opentracks/util/StringUtilsTest.java
+++ b/src/androidTest/java/de/dennisguse/opentracks/util/StringUtilsTest.java
@@ -20,6 +20,8 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -27,9 +29,23 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.time.DayOfWeek;
import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.format.TextStyle;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.GregorianCalendar;
+import java.util.Locale;
import java.util.TimeZone;
+import java.util.stream.Collectors;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Speed;
@@ -184,4 +200,82 @@ public class StringUtilsTest {
assertEquals("12.0 km/h", StringUtils.formatSpeed(context, Speed.of(3.34), true, true));
assertEquals("7.5 mph", StringUtils.formatSpeed(context, Speed.of(3.34), false, true));
}
+
+ @Test
+ public void testFormatDateTime() {
+ LocalDateTime localDateTime = LocalDateTime.parse("2022-01-02T10:15:30");
+
+ setLocale("en", "US");
+ testFormatDateTime(localDateTime, localDateTime.getMonth().getDisplayName(TextStyle.SHORT, Locale.getDefault()) + " " + localDateTime.getDayOfMonth() + ", " + localDateTime.getYear() + " 10:15:30");
+
+ setLocale("es", "ES");
+ testFormatDateTime(localDateTime, localDateTime.getDayOfMonth() + " " + localDateTime.getMonth().getDisplayName(TextStyle.SHORT, Locale.getDefault()) + " " + localDateTime.getYear() + " 10:15:30");
+
+ setLocale("de", "DE");
+ testFormatDateTime(localDateTime, String.format("%02d", localDateTime.getDayOfMonth()) + "." + String.format("%02d", localDateTime.getMonthValue()) + "." + String.format("%04d", localDateTime.getYear()) + " 10:15:30");
+ }
+
+ private void testFormatDateTime(LocalDateTime localDateTime, String expectedFormat) {
+ // given
+ int offsetFromLocale = OffsetDateTime.now().getOffset().getTotalSeconds() - 28800; // -08:00
+ OffsetDateTime localOffsetDateTime = OffsetDateTime.of(localDateTime, ZoneOffset.systemDefault().getRules().getOffset(Instant.now()));
+ OffsetDateTime outOffsetDateTime = OffsetDateTime.of(localDateTime, ZoneOffset.ofTotalSeconds(offsetFromLocale));
+
+ // when
+ String formatDate = StringUtils.formatDateTime(outOffsetDateTime);
+
+ // then
+ assertEquals(localOffsetDateTime.getOffset().getTotalSeconds(), outOffsetDateTime.getOffset().getTotalSeconds() + 28800);
+ assertEquals(formatDate, expectedFormat);
+ }
+
+ @Test
+ public void testFormatDateTodayRelative() {
+ // given
+ ArrayList shortDays = Arrays.stream(DayOfWeek.values()).map(d -> d.getDisplayName(TextStyle.FULL, Locale.getDefault())).collect(Collectors.toCollection(ArrayList::new));
+ ArrayList shortMonths = Arrays.stream(Month.values()).map(m -> m.getDisplayName(TextStyle.SHORT, Locale.getDefault())).collect(Collectors.toCollection(ArrayList::new));
+
+ LocalDate today = LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault()).toLocalDate();
+ LocalDate yesterday = today.minus(1, ChronoUnit.DAYS);
+ LocalDate dayName = today.minus(2, ChronoUnit.DAYS);
+ LocalDate thisYear = today.minus(15, ChronoUnit.DAYS);
+ LocalDate aYearAgo = today.minus(400, ChronoUnit.DAYS);
+
+ int offsetFromLocale = OffsetDateTime.now().getOffset().getTotalSeconds() - 28800; // -08:00
+
+ OffsetDateTime todayRecordedOdt = OffsetDateTime.of(LocalDateTime.of(today.getYear(), today.getMonth(), today.getDayOfMonth(), 20, 0, 0), ZoneOffset.ofTotalSeconds(offsetFromLocale));
+ OffsetDateTime yesterdayRecordedOdt = OffsetDateTime.of(LocalDateTime.of(yesterday.getYear(), yesterday.getMonth(), yesterday.getDayOfMonth(), 1, 0, 0), ZoneOffset.ofTotalSeconds(offsetFromLocale));
+ OffsetDateTime dayNameRecordedOdt = OffsetDateTime.of(LocalDateTime.of(dayName.getYear(), dayName.getMonth(), dayName.getDayOfMonth(), 7, 0, 0), ZoneOffset.ofTotalSeconds(offsetFromLocale));
+ OffsetDateTime thisYearRecordedOdt = OffsetDateTime.of(LocalDateTime.of(thisYear.getYear(), thisYear.getMonth(), thisYear.getDayOfMonth(), 12, 0, 0), ZoneOffset.ofTotalSeconds(offsetFromLocale));
+ OffsetDateTime aYearAgoRecordedOdt = OffsetDateTime.of(LocalDateTime.of(aYearAgo.getYear(), aYearAgo.getMonth(), aYearAgo.getDayOfMonth(), 17, 0, 0), ZoneOffset.ofTotalSeconds(offsetFromLocale));
+
+ // when
+ String formatToday = StringUtils.formatDateTodayRelative(context, todayRecordedOdt);
+ String formatYesterday = StringUtils.formatDateTodayRelative(context, yesterdayRecordedOdt);
+ String formatDayName = StringUtils.formatDateTodayRelative(context, dayNameRecordedOdt);
+ String formatThisYear = StringUtils.formatDateTodayRelative(context, thisYearRecordedOdt);
+ String formatAYearAgo = StringUtils.formatDateTodayRelative(context, aYearAgoRecordedOdt);
+
+ // then
+ assertEquals("Today", formatToday);
+ assertEquals("Yesterday", formatYesterday);
+ assertTrue(shortDays.contains(formatDayName)); // Something like Friday
+ if (today.getYear() != thisYear.getYear()) {
+ assertTrue(shortMonths.stream().anyMatch(fty -> formatThisYear.matches("\\d+ " + fty + " \\d{4}"))); // Something like 14 Dec 2021
+ } else {
+ assertTrue(shortMonths.stream().anyMatch(fty -> formatThisYear.matches("\\d+ " + fty))); // Something like 14 Dec
+ }
+ assertTrue(shortMonths.stream().anyMatch(fty -> formatAYearAgo.matches("\\d+ " + fty + " \\d{4}"))); // Something like 14 Dec 2021
+ }
+
+ private void setLocale(String language, String country) {
+ Locale locale = new Locale(language, country);
+ // here we update locale for date formatters
+ Locale.setDefault(locale);
+ // here we update locale for app resources
+ Resources res = context.getResources();
+ Configuration config = res.getConfiguration();
+ config.locale = locale;
+ res.updateConfiguration(config, res.getDisplayMetrics());
+ }
}
diff --git a/src/androidTest/java/de/dennisguse/opentracks/util/TrackNameUtilsTest.java b/src/androidTest/java/de/dennisguse/opentracks/util/TrackNameUtilsTest.java
index ece687a99..a0c8fb095 100644
--- a/src/androidTest/java/de/dennisguse/opentracks/util/TrackNameUtilsTest.java
+++ b/src/androidTest/java/de/dennisguse/opentracks/util/TrackNameUtilsTest.java
@@ -16,6 +16,8 @@
package de.dennisguse.opentracks.util;
+import static org.junit.Assert.assertEquals;
+
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
@@ -24,16 +26,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.text.SimpleDateFormat;
-import java.time.Instant;
-import java.util.Locale;
+import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.settings.PreferencesUtils;
-import static org.junit.Assert.assertEquals;
-
/**
* Tests {@link TrackNameUtils}.
*
@@ -43,7 +42,7 @@ import static org.junit.Assert.assertEquals;
public class TrackNameUtilsTest {
private static final Track.Id TRACK_ID = new Track.Id(1L);
- private static final Instant START_TIME = Instant.ofEpochMilli(1288213406000L);
+ private static final OffsetDateTime START_TIME = OffsetDateTime.parse("2022-01-02T10:15:30+01:00");
private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
@@ -53,7 +52,7 @@ public class TrackNameUtilsTest {
@Test
public void testTrackName_date_local() {
PreferencesUtils.setString(R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_local_value));
- assertEquals(StringUtils.formatDateTime(CONTEXT, START_TIME), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
+ assertEquals(StringUtils.formatDateTimeWithOffset(START_TIME), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
}
/**
@@ -62,8 +61,7 @@ public class TrackNameUtilsTest {
@Test
public void testTrackName_date_iso_8601() {
PreferencesUtils.setString(R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_iso_8601_value));
- SimpleDateFormat simpleDateFormat = new SimpleDateFormat(TrackNameUtils.ISO_8601_FORMAT, Locale.US);
- assertEquals(simpleDateFormat.format(START_TIME.toEpochMilli()), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
+ assertEquals(StringUtils.formatDateTimeIso8601(START_TIME.toInstant(), START_TIME.getOffset()), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
}
/**
diff --git a/src/main/java/de/dennisguse/opentracks/MarkerListActivity.java b/src/main/java/de/dennisguse/opentracks/MarkerListActivity.java
index fd0210b69..1438eb45b 100644
--- a/src/main/java/de/dennisguse/opentracks/MarkerListActivity.java
+++ b/src/main/java/de/dennisguse/opentracks/MarkerListActivity.java
@@ -326,7 +326,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
public Loader onCreateLoader(int arg0, Bundle arg1) {
final String[] PROJECTION = new String[]{MarkerColumns._ID,
MarkerColumns.NAME, MarkerColumns.DESCRIPTION, MarkerColumns.CATEGORY,
- MarkerColumns.TIME, MarkerColumns.PHOTOURL};
+ MarkerColumns.TIME, MarkerColumns.PHOTOURL, MarkerColumns.TRACKID};
if (searchQuery == null) {
if (track != null) {
diff --git a/src/main/java/de/dennisguse/opentracks/TrackListActivity.java b/src/main/java/de/dennisguse/opentracks/TrackListActivity.java
index b14ab8c14..ea862f46a 100644
--- a/src/main/java/de/dennisguse/opentracks/TrackListActivity.java
+++ b/src/main/java/de/dennisguse/opentracks/TrackListActivity.java
@@ -49,6 +49,9 @@ import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import java.time.Duration;
+import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Arrays;
@@ -190,6 +193,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
int totalTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALTIME);
int totalDistanceIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALDISTANCE);
int startTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.STARTTIME);
+ int startTimeOffsetIndex = cursor.getColumnIndexOrThrow(TracksColumns.STARTTIME_OFFSET);
int categoryIndex = cursor.getColumnIndexOrThrow(TracksColumns.CATEGORY);
int descriptionIndex = cursor.getColumnIndexOrThrow(TracksColumns.DESCRIPTION);
int markerCountIndex = cursor.getColumnIndexOrThrow(TracksColumns.MARKER_COUNT);
@@ -203,12 +207,14 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
String totalDistance = StringUtils.formatDistance(TrackListActivity.this, Distance.of(cursor.getDouble(totalDistanceIndex)), metricUnits);
int markerCount = cursor.getInt(markerCountIndex);
long startTime = cursor.getLong(startTimeIndex);
+ int startTimeOffset = cursor.getInt(startTimeOffsetIndex);
String category = icon != null && !icon.equals("") ? null : cursor.getString(categoryIndex);
String description = cursor.getString(descriptionIndex);
ListItemUtils.setListItem(TrackListActivity.this, view, isRecording, recordingStatus.isPaused(),
iconId, R.string.image_track, name, totalTime, totalDistance, markerCount,
- startTime, true, category, description, false);
+ OffsetDateTime.ofInstant(Instant.ofEpochMilli(startTime), ZoneOffset.ofTotalSeconds(startTimeOffset)),
+ category, description, false);
}
};
viewBinding.trackList.setAdapter(resourceCursorAdapter);
@@ -515,7 +521,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
@Override
public Loader onCreateLoader(int arg0, Bundle arg1) {
final String[] PROJECTION = new String[]{TracksColumns._ID, TracksColumns.NAME,
- TracksColumns.DESCRIPTION, TracksColumns.CATEGORY, TracksColumns.STARTTIME,
+ TracksColumns.DESCRIPTION, TracksColumns.CATEGORY, TracksColumns.STARTTIME, TracksColumns.STARTTIME_OFFSET,
TracksColumns.TOTALDISTANCE, TracksColumns.TOTALTIME, TracksColumns.ICON, TracksColumns.MARKER_COUNT};
final String sortOrder = TracksColumns.STARTTIME + " DESC";
diff --git a/src/main/java/de/dennisguse/opentracks/adapters/MarkerResourceCursorAdapter.java b/src/main/java/de/dennisguse/opentracks/adapters/MarkerResourceCursorAdapter.java
index 6f0c0e8e3..f33d4510f 100644
--- a/src/main/java/de/dennisguse/opentracks/adapters/MarkerResourceCursorAdapter.java
+++ b/src/main/java/de/dennisguse/opentracks/adapters/MarkerResourceCursorAdapter.java
@@ -19,9 +19,15 @@ import androidx.cursoradapter.widget.ResourceCursorAdapter;
import java.io.IOException;
import java.io.InputStream;
+import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.MarkerColumns;
+import de.dennisguse.opentracks.content.data.Track;
+import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.util.ListItemUtils;
import de.dennisguse.opentracks.util.MarkerUtils;
@@ -66,6 +72,7 @@ public class MarkerResourceCursorAdapter extends ResourceCursorAdapter implement
int categoryIndex = cursor.getColumnIndex(MarkerColumns.CATEGORY);
int descriptionIndex = cursor.getColumnIndex(MarkerColumns.DESCRIPTION);
int photoUrlIndex = cursor.getColumnIndex(MarkerColumns.PHOTOURL);
+ int trackIdIndex = cursor.getColumnIndex(MarkerColumns.TRACKID);
long id = cursor.getLong(idIndex);
int iconId = MarkerUtils.ICON_ID;
@@ -74,6 +81,7 @@ public class MarkerResourceCursorAdapter extends ResourceCursorAdapter implement
String category = cursor.getString(categoryIndex);
String description = cursor.getString(descriptionIndex);
String photoUrl = cursor.getString(photoUrlIndex);
+ long trackId = cursor.getLong(trackIdIndex);
view.setTag(String.valueOf(id));
@@ -96,7 +104,10 @@ public class MarkerResourceCursorAdapter extends ResourceCursorAdapter implement
}
}
- ListItemUtils.setListItem(activity, view, false, true, iconId, R.string.image_marker, name, null, null, 0, time, false, category, description, hasPhoto);
+ ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
+ Track track = contentProviderUtils.getTrack(new Track.Id(trackId));
+ ListItemUtils.setListItem(activity, view, false, true, iconId, R.string.image_marker, name, null, null, 0,
+ OffsetDateTime.ofInstant(Instant.ofEpochMilli(time), track.getZoneOffset()), category, description, hasPhoto);
}
public void clear() {
diff --git a/src/main/java/de/dennisguse/opentracks/content/DescriptionGenerator.java b/src/main/java/de/dennisguse/opentracks/content/DescriptionGenerator.java
index 5e0b015fe..a946d18a2 100644
--- a/src/main/java/de/dennisguse/opentracks/content/DescriptionGenerator.java
+++ b/src/main/java/de/dennisguse/opentracks/content/DescriptionGenerator.java
@@ -22,6 +22,8 @@ import android.util.Pair;
import androidx.annotation.VisibleForTesting;
import java.time.Duration;
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Distance;
@@ -150,7 +152,7 @@ public class DescriptionGenerator {
}
// Recorded time
- builder.append(context.getString(R.string.description_recorded_time, StringUtils.formatDateTime(context, stats.getStartTime())));
+ builder.append(context.getString(R.string.description_recorded_time, StringUtils.formatDateTime(OffsetDateTime.ofInstant(stats.getStartTime(), ZoneId.systemDefault()))));
builder.append(lineBreak);
return builder.toString();
diff --git a/src/main/java/de/dennisguse/opentracks/fragments/FilterDialogFragment.java b/src/main/java/de/dennisguse/opentracks/fragments/FilterDialogFragment.java
index 82531437e..13ef7c9e2 100644
--- a/src/main/java/de/dennisguse/opentracks/fragments/FilterDialogFragment.java
+++ b/src/main/java/de/dennisguse/opentracks/fragments/FilterDialogFragment.java
@@ -82,10 +82,10 @@ public class FilterDialogFragment extends DialogFragment {
TextInputEditText dateTo = layout.findViewById(R.id.filter_date_edit_text_to);
LocalDateTime firstDayThisWeek = LocalDate.now().with(WeekFields.of(Locale.getDefault()).getFirstDayOfWeek()).atStartOfDay();
- dateFrom.setText(StringUtils.formatDate(getActivity(), firstDayThisWeek));
+ dateFrom.setText(StringUtils.formatLocalDateTime(firstDayThisWeek));
datePickerFrom.init(firstDayThisWeek.getYear(), firstDayThisWeek.getMonthValue() - 1, firstDayThisWeek.getDayOfMonth(), (view, year, monthOfYear, dayOfMonth) -> {
LocalDateTime localDateTime = LocalDateTime.of(year, monthOfYear + 1, dayOfMonth, 0, 0, 0);
- dateFrom.setText(StringUtils.formatDate(getActivity(), localDateTime));
+ dateFrom.setText(StringUtils.formatLocalDateTime(localDateTime));
datePickerFrom.setVisibility(View.GONE);
datePickerTo.setMinDate(localDateTime.toInstant(ZoneOffset.ofTotalSeconds(0)).toEpochMilli());
if (localDateTime.isAfter(LocalDateTime.of(datePickerTo.getYear(), datePickerTo.getMonth() + 1, datePickerTo.getDayOfMonth(), 23, 59, 59))) {
@@ -94,10 +94,10 @@ public class FilterDialogFragment extends DialogFragment {
});
LocalDateTime lastDayThisWeek = firstDayThisWeek.plusDays(6).withHour(23).withMinute(59).withSecond(59);
- dateTo.setText(StringUtils.formatDate(getActivity(), lastDayThisWeek));
+ dateTo.setText(StringUtils.formatLocalDateTime(lastDayThisWeek));
datePickerTo.init(lastDayThisWeek.getYear(), lastDayThisWeek.getMonthValue() - 1, lastDayThisWeek.getDayOfMonth(), (view, year, monthOfYear, dayOfMonth) -> {
LocalDateTime localDateTime = LocalDateTime.of(year, monthOfYear + 1, dayOfMonth, 23, 59, 59);
- dateTo.setText(StringUtils.formatDate(getActivity(), localDateTime));
+ dateTo.setText(StringUtils.formatLocalDateTime(localDateTime));
datePickerTo.setVisibility(View.GONE);
});
diff --git a/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordedFragment.java b/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordedFragment.java
index 10b013fed..e117cc1fc 100644
--- a/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordedFragment.java
+++ b/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordedFragment.java
@@ -179,7 +179,7 @@ public class StatisticsRecordedFragment extends Fragment {
private void loadTrackDescription(@NonNull Track track) {
viewBinding.statsNameValue.setText(track.getName());
viewBinding.statsDescriptionValue.setText(track.getDescription());
- viewBinding.statsStartDatetimeValue.setText(StringUtils.formatDateTime(getContext(), track.getTrackStatistics().getStartTime()));
+ viewBinding.statsStartDatetimeValue.setText(StringUtils.formatDateTime(track.getStartTime()));
}
private void updateUI() {
diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java
index 69e3b1d65..5b3528c59 100644
--- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java
+++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java
@@ -9,6 +9,7 @@ import android.util.Pair;
import androidx.annotation.NonNull;
+import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import de.dennisguse.opentracks.R;
@@ -59,13 +60,12 @@ class TrackRecordingManager {
insertTrackPoint(trackId, segmentStartTrackPoint);
- //TODO Pass TrackPoint
- track.setName(TrackNameUtils.getTrackName(context, trackId, segmentStartTrackPoint.getTime()));
-
String category = PreferencesUtils.getDefaultActivity();
track.setCategory(category);
track.setIcon(TrackIconUtils.getIconValue(context, category));
track.setTrackStatistics(trackStatisticsUpdater.getTrackStatistics());
+ //TODO Pass TrackPoint
+ track.setName(TrackNameUtils.getTrackName(context, trackId, track.getStartTime()));
contentProviderUtils.updateTrack(track);
currentSegmentHasTrackPoint = false;
diff --git a/src/main/java/de/dennisguse/opentracks/util/ListItemUtils.java b/src/main/java/de/dennisguse/opentracks/util/ListItemUtils.java
index 83180dfc7..0b15d0b55 100644
--- a/src/main/java/de/dennisguse/opentracks/util/ListItemUtils.java
+++ b/src/main/java/de/dennisguse/opentracks/util/ListItemUtils.java
@@ -17,8 +17,6 @@
package de.dennisguse.opentracks.util;
import android.content.Context;
-import android.graphics.BitmapFactory;
-import android.text.format.DateUtils;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
@@ -26,6 +24,9 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
+import java.time.OffsetDateTime;
+import java.time.format.DateTimeFormatter;
+
import de.dennisguse.opentracks.R;
/**
@@ -51,13 +52,12 @@ public class ListItemUtils {
* @param totalTime the total time value
* @param totalDistance the total distance value
* @param markerCount the marker count
- * @param startTime the start time value
- * @param useRelativeTime true to display relative time if appropriate
+ * @param offsetDateTime the start time with offset
* @param category the category value
* @param description the description value
* @param hasPhoto true if this list item has photo
*/
- public static void setListItem(Context context, View view, boolean isRecording, boolean isPaused, int iconId, int iconContentDescriptionId, String name, String totalTime, String totalDistance, int markerCount, long startTime, boolean useRelativeTime, String category, String description, boolean hasPhoto) {
+ public static void setListItem(Context context, View view, boolean isRecording, boolean isPaused, int iconId, int iconContentDescriptionId, String name, String totalTime, String totalDistance, int markerCount, OffsetDateTime offsetDateTime, String category, String description, boolean hasPhoto) {
// Set icon
if (isRecording) {
iconId = isPaused ? R.drawable.ic_track_paused : R.drawable.ic_track_recording;
@@ -104,12 +104,16 @@ public class ListItemUtils {
setTextView(context, markerCountTextView, markerCountValue, hasPhoto);
// Set date/time
- String[] dateTime = getDateTime(isRecording, context, startTime, useRelativeTime);
TextView dateTextView = view.findViewById(R.id.list_item_date);
- setTextView(context, dateTextView, dateTime[0], hasPhoto);
-
TextView timeTextView = view.findViewById(R.id.list_item_time);
- setTextView(context, timeTextView, dateTime[1], hasPhoto);
+ String dateValue = null;
+ String timeValue = null;
+ if (!isRecording) {
+ dateValue = StringUtils.formatDateTodayRelative(context, offsetDateTime);
+ timeValue = offsetDateTime.format(DateTimeFormatter.ofPattern("hh:mm"));
+ }
+ setTextView(context, dateTextView, dateValue, hasPhoto);
+ setTextView(context, timeTextView, timeValue, hasPhoto);
// Set category and description
TextView categoryDescriptionTextView = view.findViewById(R.id.list_item_category_description);
@@ -133,27 +137,6 @@ public class ListItemUtils {
params.gravity = timeDistanceTextView.getVisibility() == View.GONE && markerCountIcon.getVisibility() == View.GONE ? Gravity.TOP : Gravity.CENTER_VERTICAL;
}
- private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
- // Raw height and width of image
- final int height = options.outHeight;
- final int width = options.outWidth;
- int inSampleSize = 1;
-
- if (height > reqHeight || width > reqWidth) {
-
- final int halfHeight = height / 2;
- final int halfWidth = width / 2;
-
- // Calculate the largest inSampleSize value that is a power of 2 and keeps both height and width larger than the requested height and width.
- while ((halfHeight / inSampleSize) >= reqHeight
- && (halfWidth / inSampleSize) >= reqWidth) {
- inSampleSize *= 2;
- }
- }
-
- return inSampleSize;
- }
-
/**
* Gets a string for share owner, total time, and total distance.
*
@@ -177,30 +160,6 @@ public class ListItemUtils {
return builder.toString();
}
- /**
- * Gets the date and time as an array of two strings.
- *
- * @param isRecording true if recording
- * @param context the context
- * @param time the start time
- */
- private static String[] getDateTime(boolean isRecording, Context context, long time, boolean useRelativeTime) {
- if (isRecording || time == 0L) {
- return new String[]{null, null};
- }
-
- boolean isToday = DateUtils.isToday(time);
- int timeFlags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL;
-
- if (isToday && useRelativeTime) {
- return new String[]{DateUtils.getRelativeTimeSpanString(time, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE).toString(), null};
- } else if (isToday) {
- return new String[]{DateUtils.formatDateTime(context, time, timeFlags), null};
- }
-
- return new String[]{DateUtils.formatDateTime(context, time, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_ALL), DateUtils.formatDateTime(context, time, timeFlags)};
- }
-
/**
* Sets a text view.
*
diff --git a/src/main/java/de/dennisguse/opentracks/util/StringUtils.java b/src/main/java/de/dennisguse/opentracks/util/StringUtils.java
index 30b48caf5..65aa39d24 100644
--- a/src/main/java/de/dennisguse/opentracks/util/StringUtils.java
+++ b/src/main/java/de/dennisguse/opentracks/util/StringUtils.java
@@ -15,6 +15,8 @@
*/
package de.dennisguse.opentracks.util;
+import static java.time.temporal.ChronoUnit.DAYS;
+
import android.content.Context;
import android.location.Location;
import android.text.TextUtils;
@@ -28,12 +30,14 @@ import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.time.Duration;
import java.time.Instant;
+import java.time.LocalDate;
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;
+import java.time.format.FormatStyle;
import java.time.temporal.TemporalAccessor;
import de.dennisguse.opentracks.R;
@@ -54,18 +58,47 @@ public class StringUtils {
}
/**
- * Formats the date and time based on user's phone date/time preferences.
+ * Formats the date and time of the OffsetDateTime (using default Locale format)
*/
- public static String formatDateTime(Context context, Instant time) {
- return DateUtils.formatDateTime(context, time.toEpochMilli(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NUMERIC_DATE)
- + " " + DateUtils.formatDateTime(context, time.toEpochMilli(), DateUtils.FORMAT_SHOW_TIME);
+ public static String formatDateTime(OffsetDateTime odt) {
+ return odt.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM));
}
/**
- * Formats the date and time based on user's phone date/time preferences.
+ * Formats the date and time with the offset (using default Locale format).
*/
- public static String formatDate(Context context, LocalDateTime localDateTime) {
- return DateUtils.formatDateTime(context, localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(), DateUtils.FORMAT_SHOW_DATE);
+ public static String formatDateTimeWithOffset(OffsetDateTime odt) {
+ return odt.toZonedDateTime().format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL));
+ }
+
+ public static String formatLocalDateTime(LocalDateTime localDateTime) {
+ return localDateTime.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL));
+ }
+
+ /**
+ * Formats the date relative to today date.
+ */
+ public static String formatDateTodayRelative(Context context, OffsetDateTime odt) {
+ LocalDate today = LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault()).toLocalDate();
+ LocalDate ld = odt.toLocalDate();
+ long daysBetween = DAYS.between(ld, today);
+
+ if (daysBetween == 0) {
+ // Today
+ return context.getString(R.string.generic_today);
+ } else if (daysBetween == 1) {
+ // Yesterday
+ return context.getString(R.string.generic_yesterday);
+ } else if (daysBetween < 7) {
+ // Name of the week day
+ return ld.format(DateTimeFormatter.ofPattern("EEEE"));
+ } else if (today.getYear() == ld.getYear()) {
+ // Short date without year
+ return ld.format(DateTimeFormatter.ofPattern("d MMM"));
+ } else {
+ // Short date with year
+ return ld.format(DateTimeFormatter.ofPattern("d MMM y"));
+ }
}
/**
diff --git a/src/main/java/de/dennisguse/opentracks/util/TrackNameUtils.java b/src/main/java/de/dennisguse/opentracks/util/TrackNameUtils.java
index 636927844..58143a0d9 100644
--- a/src/main/java/de/dennisguse/opentracks/util/TrackNameUtils.java
+++ b/src/main/java/de/dennisguse/opentracks/util/TrackNameUtils.java
@@ -18,11 +18,7 @@ package de.dennisguse.opentracks.util;
import android.content.Context;
-import androidx.annotation.VisibleForTesting;
-
-import java.text.SimpleDateFormat;
-import java.time.Instant;
-import java.util.Locale;
+import java.time.OffsetDateTime;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
@@ -35,21 +31,17 @@ import de.dennisguse.opentracks.settings.PreferencesUtils;
*/
public class TrackNameUtils {
- //TODO Could be available in java.time?
- @VisibleForTesting
- static final String ISO_8601_FORMAT = "yyyy-MM-dd HH:mm";
-
private TrackNameUtils() {
}
//TODO Should not access sharedPreferences; trackName should be an ENUM.
- public static String getTrackName(Context context, Track.Id trackId, Instant startTime) {
+ public static String getTrackName(Context context, Track.Id trackId, OffsetDateTime startTime) {
String trackName = PreferencesUtils.getString(R.string.track_name_key, context.getString(R.string.track_name_default));
if (trackName.equals(context.getString(R.string.settings_recording_track_name_date_local_value))) {
- return StringUtils.formatDateTime(context, startTime);
+ return StringUtils.formatDateTimeWithOffset(startTime);
} else if (trackName.equals(context.getString(R.string.settings_recording_track_name_date_iso_8601_value))) {
- return new SimpleDateFormat(ISO_8601_FORMAT, Locale.US).format(startTime.toEpochMilli());
+ return StringUtils.formatDateTimeIso8601(startTime.toInstant(), startTime.getOffset());
} else {
return context.getString(R.string.track_name_format, trackId.getId());
}
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index e1385d281..ece3f8480 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -244,6 +244,8 @@ limitations under the License.
Filter
From
To
+ Today
+ Yesterday
Starting GPS
Waiting for a better GPS signal