Index: trunk/src/org/openstreetmap/josm/data/gpx/GpxData.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/gpx/GpxData.java	(revision 17714)
+++ trunk/src/org/openstreetmap/josm/data/gpx/GpxData.java	(revision 17715)
@@ -4,4 +4,5 @@
 import java.io.File;
 import java.text.MessageFormat;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -9,5 +10,4 @@
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -200,11 +200,11 @@
                 boolean split = false;
                 WayPoint prevLastOwnWp = null;
-                Date prevWpTime = null;
+                Instant prevWpTime = null;
                 for (WayPoint wp : wpsOld) {
-                    Date wpTime = wp.getDate();
+                    Instant wpTime = wp.getInstant();
                     boolean overlap = false;
                     if (wpTime != null) {
                         for (GpxTrackSegmentSpan ownspan : getSegmentSpans()) {
-                            if (wpTime.after(ownspan.firstTime) && wpTime.before(ownspan.lastTime)) {
+                            if (wpTime.isAfter(ownspan.firstTime) && wpTime.isBefore(ownspan.lastTime)) {
                                 overlap = true;
                                 if (connect) {
@@ -219,6 +219,6 @@
                                 break;
                             } else if (connect && prevWpTime != null
-                                    && prevWpTime.before(ownspan.firstTime)
-                                    && wpTime.after(ownspan.lastTime)) {
+                                    && prevWpTime.isBefore(ownspan.firstTime)
+                                    && wpTime.isAfter(ownspan.lastTime)) {
                                 // the overlapping high priority track is shorter than the distance
                                 // between two waypoints of the low priority track
@@ -287,6 +287,6 @@
     static class GpxTrackSegmentSpan {
 
-        final Date firstTime;
-        final Date lastTime;
+        final Instant firstTime;
+        final Instant lastTime;
         private final boolean inv;
         private final WayPoint firstWp;
@@ -294,7 +294,7 @@
 
         GpxTrackSegmentSpan(WayPoint a, WayPoint b) {
-            Date at = a.getDate();
-            Date bt = b.getDate();
-            inv = bt.before(at);
+            Instant at = a.getInstant();
+            Instant bt = b.getInstant();
+            inv = bt.isBefore(at);
             if (inv) {
                 firstWp = b;
@@ -333,6 +333,6 @@
 
         boolean overlapsWith(GpxTrackSegmentSpan other) {
-            return (firstTime.before(other.lastTime) && other.firstTime.before(lastTime))
-                || (other.firstTime.before(lastTime) && firstTime.before(other.lastTime));
+            return (firstTime.isBefore(other.lastTime) && other.firstTime.isBefore(lastTime))
+                || (other.firstTime.isBefore(lastTime) && firstTime.isBefore(other.lastTime));
         }
 
@@ -720,5 +720,5 @@
      * @return  minimum and maximum dates in array of 2 elements
      */
-    public static Date[] getMinMaxTimeForTrack(IGpxTrack trk) {
+    public static Instant[] getMinMaxTimeForTrack(IGpxTrack trk) {
         final LongSummaryStatistics statistics = trk.getSegments().stream()
                 .flatMap(seg -> seg.getWayPoints().stream())
@@ -727,5 +727,5 @@
         return statistics.getCount() == 0 || (statistics.getMin() == 0 && statistics.getMax() == 0)
                 ? null
-                : new Date[]{new Date(statistics.getMin()), new Date(statistics.getMax())};
+                : new Instant[]{Instant.ofEpochMilli(statistics.getMin()), Instant.ofEpochMilli(statistics.getMax())};
     }
 
@@ -737,5 +737,5 @@
     * @since 7319
     */
-    public synchronized Date[] getMinMaxTimeForAllTracks() {
+    public synchronized Instant[] getMinMaxTimeForAllTracks() {
         long now = System.currentTimeMillis();
         final LongSummaryStatistics statistics = tracks.stream()
@@ -746,6 +746,6 @@
                 .summaryStatistics();
         return statistics.getCount() == 0
-                ? new Date[0]
-                : new Date[]{new Date(statistics.getMin()), new Date(statistics.getMax())};
+                ? new Instant[0]
+                : new Instant[]{Instant.ofEpochMilli(statistics.getMin()), Instant.ofEpochMilli(statistics.getMax())};
     }
 
Index: trunk/src/org/openstreetmap/josm/data/gpx/GpxImageCorrelation.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/gpx/GpxImageCorrelation.java	(revision 17714)
+++ trunk/src/org/openstreetmap/josm/data/gpx/GpxImageCorrelation.java	(revision 17715)
@@ -3,5 +3,4 @@
 
 import java.util.ArrayList;
-import java.util.Date;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
@@ -226,5 +225,5 @@
                 final GpxImageEntry curImg = images.get(i);
                 final GpxImageEntry curTmp = curImg.getTmp();
-                final long time = curImg.getExifTime().getTime();
+                final long time = curImg.getExifInstant().toEpochMilli();
                 if ((!isLast && time > curWpTime) || time < prevWpTime) {
                     break;
@@ -239,5 +238,5 @@
                         curTmp.setPos(curWp.getCoor());
                     }
-                    curTmp.setGpsTime(new Date(curImg.getExifTime().getTime() - offset));
+                    curTmp.setGpsTime(curImg.getExifInstant().minusMillis(offset));
                     curTmp.flagNewGpsData();
                     curImg.tmpUpdated();
@@ -253,5 +252,5 @@
                 GpxImageEntry curImg = images.get(i);
                 GpxImageEntry curTmp = curImg.getTmp();
-                final long imgTime = curImg.getExifTime().getTime();
+                final long imgTime = curImg.getExifInstant().toEpochMilli();
                 if (imgTime < prevWpTime) {
                     break;
@@ -265,5 +264,5 @@
                         curTmp.setElevation(prevElevation + (curElevation - prevElevation) * timeDiff);
                     }
-                    curTmp.setGpsTime(new Date(curImg.getExifTime().getTime() - offset));
+                    curTmp.setGpsTime(curImg.getExifInstant().minusMillis(offset));
                     curTmp.flagNewGpsData();
                     curImg.tmpUpdated();
@@ -281,9 +280,9 @@
 
         // No photos or the first photo taken is later than the search period
-        if (lstSize == 0 || searchedTime < images.get(0).getExifTime().getTime())
+        if (lstSize == 0 || searchedTime < images.get(0).getExifInstant().toEpochMilli())
             return -1;
 
         // The search period is later than the last photo
-        if (searchedTime > images.get(lstSize - 1).getExifTime().getTime())
+        if (searchedTime > images.get(lstSize - 1).getExifInstant().toEpochMilli())
             return lstSize-1;
 
@@ -294,5 +293,5 @@
         while (endIndex - startIndex > 1) {
             curIndex = (endIndex + startIndex) / 2;
-            if (searchedTime > images.get(curIndex).getExifTime().getTime()) {
+            if (searchedTime > images.get(curIndex).getExifInstant().toEpochMilli()) {
                 startIndex = curIndex;
             } else {
@@ -300,10 +299,10 @@
             }
         }
-        if (searchedTime < images.get(endIndex).getExifTime().getTime())
+        if (searchedTime < images.get(endIndex).getExifInstant().toEpochMilli())
             return startIndex;
 
         // This final loop is to check if photos with the exact same EXIF time follows
-        while ((endIndex < (lstSize - 1)) && (images.get(endIndex).getExifTime().getTime()
-                == images.get(endIndex + 1).getExifTime().getTime())) {
+        while ((endIndex < (lstSize - 1)) && (images.get(endIndex).getExifInstant().toEpochMilli()
+                == images.get(endIndex + 1).getExifInstant().toEpochMilli())) {
             endIndex++;
         }
Index: trunk/src/org/openstreetmap/josm/data/gpx/GpxImageEntry.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/gpx/GpxImageEntry.java	(revision 17714)
+++ trunk/src/org/openstreetmap/josm/data/gpx/GpxImageEntry.java	(revision 17715)
@@ -6,4 +6,5 @@
 import java.io.File;
 import java.io.IOException;
+import java.time.Instant;
 import java.util.Date;
 import java.util.List;
@@ -43,5 +44,5 @@
     private LatLon exifCoor;
     private Double exifImgDir;
-    private Date exifTime;
+    private Instant exifTime;
     /**
      * Flag isNewGpsData indicates that the GPS data of the image is new or has changed.
@@ -51,5 +52,5 @@
     private boolean isNewGpsData;
     /** Temporary source of GPS time if not correlated with GPX track. */
-    private Date exifGpsTime;
+    private Instant exifGpsTime;
 
     private String iptcCaption;
@@ -68,5 +69,5 @@
     private Double elevation;
     /** The time after correlation with a gpx track */
-    private Date gpsTime;
+    private Instant gpsTime;
 
     private int width;
@@ -173,5 +174,7 @@
      * is returned if that copy exists.
      * @return the GPS time value
-     */
+     * @deprecated Use {@link #getGpsInstant}
+     */
+    @Deprecated
     public Date getGpsTime() {
         if (tmp != null)
@@ -181,4 +184,13 @@
 
     /**
+     * Returns the GPS time value. The GPS time value from the temporary copy
+     * is returned if that copy exists.
+     * @return the GPS time value
+     */
+    public Instant getGpsInstant() {
+        return tmp != null ? tmp.gpsTime : gpsTime;
+    }
+
+    /**
      * Convenient way to determine if this entry has a GPS time, without the cost of building a defensive copy.
      * @return {@code true} if this entry has a GPS time
@@ -208,7 +220,17 @@
      * Returns EXIF time
      * @return EXIF time
-     */
+     * @deprecated Use {@link #getExifInstant}
+     */
+    @Deprecated
     public Date getExifTime() {
         return getDefensiveDate(exifTime);
+    }
+
+    /**
+     * Returns EXIF time
+     * @return EXIF time
+     */
+    public Instant getExifInstant() {
+        return exifTime;
     }
 
@@ -226,7 +248,17 @@
      * @return the EXIF GPS time
      * @since 6392
-     */
+     * @deprecated Use {@link #getExifGpsInstant}
+     */
+    @Deprecated
     public Date getExifGpsTime() {
         return getDefensiveDate(exifGpsTime);
+    }
+
+    /**
+     * Returns the EXIF GPS time.
+     * @return the EXIF GPS time
+     */
+    public Instant getExifGpsInstant() {
+        return exifGpsTime;
     }
 
@@ -240,8 +272,8 @@
     }
 
-    private static Date getDefensiveDate(Date date) {
+    private static Date getDefensiveDate(Instant date) {
         if (date == null)
             return null;
-        return new Date(date.getTime());
+        return Date.from(date);
     }
 
@@ -325,7 +357,9 @@
      * Sets EXIF time.
      * @param exifTime EXIF time
-     */
+     * @deprecated Use {@link #setExifTime(Instant)}
+     */
+    @Deprecated
     public void setExifTime(Date exifTime) {
-        this.exifTime = getDefensiveDate(exifTime);
+        this.exifTime = exifTime == null ? null : exifTime.toInstant();
     }
 
@@ -334,11 +368,43 @@
      * @param exifGpsTime the EXIF GPS time
      * @since 6392
-     */
+     * @deprecated Use {@link #setExifGpsTime(Instant)}
+     */
+    @Deprecated
     public void setExifGpsTime(Date exifGpsTime) {
-        this.exifGpsTime = getDefensiveDate(exifGpsTime);
-    }
-
+        this.exifGpsTime = exifGpsTime == null ? null : exifGpsTime.toInstant();
+    }
+
+    /**
+     * Sets the GPS time.
+     * @param gpsTime the GPS time
+     * @deprecated Use {@link #setGpsTime(Instant)}
+     */
+    @Deprecated
     public void setGpsTime(Date gpsTime) {
-        this.gpsTime = getDefensiveDate(gpsTime);
+        this.gpsTime = gpsTime == null ? null : gpsTime.toInstant();
+    }
+
+    /**
+     * Sets EXIF time.
+     * @param exifTime EXIF time
+     */
+    public void setExifTime(Instant exifTime) {
+        this.exifTime = exifTime;
+    }
+
+    /**
+     * Sets the EXIF GPS time.
+     * @param exifGpsTime the EXIF GPS time
+     */
+    public void setExifGpsTime(Instant exifGpsTime) {
+        this.exifGpsTime = exifGpsTime;
+    }
+
+    /**
+     * Sets the GPS time.
+     * @param gpsTime the GPS time
+     */
+    public void setGpsTime(Instant gpsTime) {
+        this.gpsTime = gpsTime;
     }
 
@@ -635,5 +701,5 @@
                         Logging.warn(topException);
                         Logging.info(tr("Can''t parse metadata for file \"{0}\". Using last modified date as timestamp.", fn));
-                        setExifTime(new Date(file.lastModified()));
+                        setExifTime(Instant.ofEpochMilli(file.lastModified()));
                         setExifCoor(null);
                         setPos(null);
@@ -646,7 +712,7 @@
         // Changed to silently cope with no time info in exif. One case
         // of person having time that couldn't be parsed, but valid GPS info
-        Date time = null;
+        Instant time = null;
         try {
-            time = ExifReader.readTime(metadata);
+            time = ExifReader.readInstant(metadata);
         } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException ex) {
             Logging.warn(ex);
@@ -655,5 +721,5 @@
         if (time == null) {
             Logging.info(tr("No EXIF time in file \"{0}\". Using last modified date as timestamp.", fn));
-            time = new Date(file.lastModified()); //use lastModified time if no EXIF time present
+            time = Instant.ofEpochMilli(file.lastModified()); //use lastModified time if no EXIF time present
         }
         setExifTime(time);
@@ -705,5 +771,5 @@
         }
 
-        ifNotNull(dirGps.getGpsDate(), this::setExifGpsTime);
+        ifNotNull(dirGps.getGpsDate(), d -> setExifGpsTime(d.toInstant()));
 
         IptcDirectory dirIptc = metadata.getFirstDirectoryOfType(IptcDirectory.class);
Index: trunk/src/org/openstreetmap/josm/data/gpx/WayPoint.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/gpx/WayPoint.java	(revision 17714)
+++ trunk/src/org/openstreetmap/josm/data/gpx/WayPoint.java	(revision 17715)
@@ -3,4 +3,5 @@
 
 import java.awt.Color;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Date;
@@ -16,5 +17,4 @@
 import org.openstreetmap.josm.tools.Logging;
 import org.openstreetmap.josm.tools.Utils;
-import org.openstreetmap.josm.tools.date.DateUtils;
 import org.openstreetmap.josm.tools.template_engine.TemplateEngineDataProvider;
 
@@ -66,7 +66,4 @@
         attr = new HashMap<>(0);
         attr.putAll(p.attr);
-        if (p.getDate() != null) {
-            attr.put(PT_TIME, p.getDate());
-        }
         lat = p.lat;
         lon = p.lon;
@@ -139,7 +136,9 @@
      * @param time the time to set
      * @since 9383
-     */
+     * @deprecated Use {@link #setInstant(Instant)}
+     */
+    @Deprecated
     public void setTime(Date time) {
-        setTimeInMillis(time.getTime());
+        setInstant(time.toInstant());
     }
 
@@ -149,7 +148,9 @@
      * @param ts seconds from the epoch
      * @since 13210
-     */
+     * @deprecated Use {@link #setInstant(Instant)}
+     */
+    @Deprecated
     public void setTime(long ts) {
-        setTimeInMillis(ts * 1000);
+        setInstant(Instant.ofEpochSecond(ts));
     }
 
@@ -161,5 +162,14 @@
      */
     public void setTimeInMillis(long ts) {
-        attr.put(PT_TIME, new Date(ts));
+        setInstant(Instant.ofEpochMilli(ts));
+    }
+
+    /**
+     * Sets the {@link #PT_TIME} attribute to the specified time.
+     *
+     * @param instant the time to set
+     */
+    public void setInstant(Instant instant) {
+        attr.put(PT_TIME, instant);
     }
 
@@ -185,6 +195,6 @@
      */
     public long getTimeInMillis() {
-        Date d = getDateImpl();
-        return d == null ? 0 : d.getTime();
+        Instant d = getInstant();
+        return d == null ? 0 : d.toEpochMilli();
     }
 
@@ -196,5 +206,5 @@
      */
     public boolean hasDate() {
-        return attr.get(PT_TIME) instanceof Date;
+        return attr.get(PT_TIME) instanceof Instant;
     }
 
@@ -204,20 +214,24 @@
      * @return a copy of the Date object associated with this waypoint
      * @since 14456
-     */
+     * @deprecated Use {@link #getInstant()}
+     */
+    @Deprecated
     public Date getDate() {
-        return DateUtils.cloneDate(getDateImpl());
-    }
-
-    /**
-     * Returns the waypoint time Date object.
-     *
-     * @return the Date object associated with this waypoint
-     */
-    private Date getDateImpl() {
+        Instant instant = getInstant();
+        return instant == null ? null : Date.from(instant);
+    }
+
+    /**
+     * Returns the waypoint instant.
+     *
+     * @return the instant associated with this waypoint
+     * @since 14456
+     */
+    public Instant getInstant() {
         if (attr != null) {
             final Object obj = attr.get(PT_TIME);
 
-            if (obj instanceof Date) {
-                return (Date) obj;
+            if (obj instanceof Instant) {
+                return (Instant) obj;
             } else if (obj == null) {
                 Logging.trace("Waypoint {0} value unset", PT_TIME);
Index: trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java	(revision 17714)
+++ trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java	(revision 17715)
@@ -10,5 +10,8 @@
 import java.awt.event.ActionEvent;
 import java.io.File;
-import java.text.DateFormat;
+import java.time.Instant;
+import java.time.format.DateTimeFormatter;
+import java.time.format.FormatStyle;
+import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -157,5 +160,5 @@
      */
     public static String getTimespanForTrack(IGpxTrack trk) {
-        Date[] bounds = GpxData.getMinMaxTimeForTrack(trk);
+        Instant[] bounds = GpxData.getMinMaxTimeForTrack(trk);
         return bounds != null ? formatTimespan(bounds) : "";
     }
@@ -166,20 +169,20 @@
      * @return The timespan as a string
      */
-    public static String formatTimespan(Date[] bounds) {
+    public static String formatTimespan(Instant[] bounds) {
         String ts = "";
-        DateFormat df = DateUtils.getDateFormat(DateFormat.SHORT);
+        DateTimeFormatter df = DateUtils.getDateFormatter(FormatStyle.SHORT);
         String earliestDate = df.format(bounds[0]);
         String latestDate = df.format(bounds[1]);
 
         if (earliestDate.equals(latestDate)) {
-            DateFormat tf = DateUtils.getTimeFormat(DateFormat.SHORT);
+            DateTimeFormatter tf = DateUtils.getTimeFormatter(FormatStyle.SHORT);
             ts += earliestDate + ' ';
             ts += tf.format(bounds[0]) + " - " + tf.format(bounds[1]);
         } else {
-            DateFormat dtf = DateUtils.getDateTimeFormat(DateFormat.SHORT, DateFormat.MEDIUM);
+            DateTimeFormatter dtf = DateUtils.getDateTimeFormatter(FormatStyle.SHORT, FormatStyle.MEDIUM);
             ts += dtf.format(bounds[0]) + " - " + dtf.format(bounds[1]);
         }
 
-        int diff = (int) (bounds[1].getTime() - bounds[0].getTime()) / 1000;
+        long diff = ChronoUnit.SECONDS.between(bounds[1], bounds[0]);
         ts += String.format(" (%d:%02d)", diff / 3600, (diff % 3600) / 60);
         return ts;
@@ -355,8 +358,8 @@
         long to = toDate.getTime();
         for (IGpxTrack trk : data.getTracks()) {
-            Date[] t = GpxData.getMinMaxTimeForTrack(trk);
+            Instant[] t = GpxData.getMinMaxTimeForTrack(trk);
 
             if (t == null) continue;
-            long tm = t[1].getTime();
+            long tm = t[1].toEpochMilli();
             trackVisibility[i] = (tm == 0 && showWithoutDate) || (from <= tm && tm <= to);
             i++;
Index: trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java	(revision 17714)
+++ trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java	(revision 17715)
@@ -24,4 +24,5 @@
 import java.io.IOException;
 import java.time.DateTimeException;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -878,5 +879,5 @@
                 wpt.setTimeInMillis(DateUtils.tsFromString(v));
             } else if (!n.isTimestampEmpty()) {
-                wpt.setTime(Integer.toUnsignedLong(n.getRawTimestamp()));
+                wpt.setInstant(Instant.ofEpochSecond(Integer.toUnsignedLong(n.getRawTimestamp())));
             }
         } catch (UncheckedParseException | DateTimeException e) {
Index: trunk/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java	(revision 17714)
+++ trunk/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java	(revision 17715)
@@ -28,9 +28,12 @@
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.time.format.FormatStyle;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.Date;
 import java.util.Dictionary;
 import java.util.Hashtable;
@@ -768,8 +771,8 @@
         void updateExifComponents(ImageEntry img) {
             imgDisp.setImage(img);
-            Date date = img.getExifTime();
+            Instant date = img.getExifInstant();
             if (date != null) {
-                DateFormat df = DateUtils.getDateTimeFormat(DateFormat.SHORT, DateFormat.MEDIUM);
-                df.setTimeZone(DateUtils.UTC); // EXIF data does not contain timezone information and is read as UTC
+                DateTimeFormatter df = DateUtils.getDateTimeFormatter(FormatStyle.SHORT, FormatStyle.MEDIUM)
+                        .withZone(ZoneOffset.UTC); // EXIF data does not contain timezone information and is read as UTC
                 lbExifTime.setText(df.format(date));
                 tfGpsTime.setText(df.format(date));
@@ -1260,5 +1263,5 @@
 
         // Init variables
-        long firstExifDate = imgs.get(0).getExifTime().getTime();
+        long firstExifDate = imgs.get(0).getExifInstant().toEpochMilli();
 
         // Finds first GPX point
@@ -1334,5 +1337,5 @@
                 .filter(e -> e.getExifCoor() == null || exif)
                 .filter(e -> tagged || !e.isTagged() || e.getExifCoor() != null)
-                .sorted(Comparator.comparing(ImageEntry::getExifTime))
+                .sorted(Comparator.comparing(ImageEntry::getExifInstant))
                 .collect(Collectors.toList());
     }
Index: trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java	(revision 17714)
+++ trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java	(revision 17715)
@@ -13,6 +13,7 @@
 import java.awt.event.KeyEvent;
 import java.awt.event.WindowEvent;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.time.format.FormatStyle;
 import java.util.Collections;
 import java.util.List;
@@ -472,21 +473,14 @@
             }
 
-            DateFormat dtf = DateUtils.getDateTimeFormat(DateFormat.SHORT, DateFormat.MEDIUM);
-            // Make sure date/time format includes milliseconds
-            if (dtf instanceof SimpleDateFormat) {
-                String pattern = ((SimpleDateFormat) dtf).toPattern();
-                if (!pattern.contains(".SSS")) {
-                    dtf = new SimpleDateFormat(pattern.replace(":ss", ":ss.SSS"));
-                }
-            }
-            // Set timezone to UTC since UTC is assumed when parsing the EXIF timestamp,
-            // see see org.openstreetmap.josm.tools.ExifReader.readTime(com.drew.metadata.Metadata)
-            dtf.setTimeZone(DateUtils.UTC);
+            DateTimeFormatter dtf = DateUtils.getDateTimeFormatter(FormatStyle.SHORT, FormatStyle.MEDIUM)
+                    // Set timezone to UTC since UTC is assumed when parsing the EXIF timestamp,
+                    // see see org.openstreetmap.josm.tools.ExifReader.readTime(com.drew.metadata.Metadata)
+                    .withZone(ZoneOffset.UTC);
 
             if (entry.hasExifTime()) {
-                osd.append(tr("\nEXIF time: {0}", dtf.format(entry.getExifTime())));
+                osd.append(tr("\nEXIF time: {0}", dtf.format(entry.getExifInstant())));
             }
             if (entry.hasGpsTime()) {
-                osd.append(tr("\nGPS time: {0}", dtf.format(entry.getGpsTime())));
+                osd.append(tr("\nGPS time: {0}", dtf.format(entry.getGpsInstant())));
             }
             Optional.ofNullable(entry.getIptcCaption()).map(s -> tr("\nCaption: {0}", s)).ifPresent(osd::append);
Index: trunk/src/org/openstreetmap/josm/gui/layer/gpx/ChooseTrackVisibilityAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/gpx/ChooseTrackVisibilityAction.java	(revision 17714)
+++ trunk/src/org/openstreetmap/josm/gui/layer/gpx/ChooseTrackVisibilityAction.java	(revision 17715)
@@ -13,4 +13,5 @@
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
+import java.time.Instant;
 import java.util.Arrays;
 import java.util.Comparator;
@@ -88,5 +89,5 @@
             String name = (String) Optional.ofNullable(attr.get(GpxConstants.GPX_NAME)).orElse("");
             String desc = (String) Optional.ofNullable(attr.get(GpxConstants.GPX_DESC)).orElse("");
-            Date[] time = GpxData.getMinMaxTimeForTrack(trk);
+            Instant[] time = GpxData.getMinMaxTimeForTrack(trk);
             String url = (String) Optional.ofNullable(attr.get("url")).orElse("");
             tracks[i] = new Object[]{name, desc, time, trk.length(), url, trk};
@@ -140,5 +141,5 @@
         t.setRowSorter(rowSorter);
         rowSorter.setModel(model);
-        rowSorter.setComparator(2, Comparator.comparing((Date[] d) -> d == null ? Long.MIN_VALUE : d[0].getTime()));
+        rowSorter.setComparator(2, Comparator.comparing((Instant[] d) -> d == null ? Instant.MIN : d[0]));
         rowSorter.setComparator(3, Comparator.comparingDouble(length -> (double) length));
         // default column widths
@@ -150,6 +151,6 @@
             public Component getTableCellRendererComponent(
                     JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
-                if (value instanceof Date[]) {
-                    value = GpxLayer.formatTimespan(((Date[]) value));
+                if (value instanceof Instant[]) {
+                    value = GpxLayer.formatTimespan(((Instant[]) value));
                 }
                 return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
Index: trunk/src/org/openstreetmap/josm/gui/layer/gpx/ConvertFromGpxLayerAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/gpx/ConvertFromGpxLayerAction.java	(revision 17714)
+++ trunk/src/org/openstreetmap/josm/gui/layer/gpx/ConvertFromGpxLayerAction.java	(revision 17715)
@@ -7,4 +7,5 @@
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Date;
@@ -36,5 +37,4 @@
 import org.openstreetmap.josm.spi.preferences.Config;
 import org.openstreetmap.josm.tools.GBC;
-import org.openstreetmap.josm.tools.date.DateUtils;
 
 /**
@@ -123,5 +123,5 @@
             String key = entry.getKey();
             Object obj = entry.getValue();
-            if (check && !keys.contains(key) && (obj instanceof String || obj instanceof Number || obj instanceof Date)) {
+            if (check && !keys.contains(key) && (obj instanceof String || obj instanceof Number || obj instanceof Instant)) {
                 keys.add(key);
             }
@@ -129,11 +129,11 @@
                 // only convert when required
                 p.put(GpxConstants.GPX_PREFIX + key, obj.toString());
-            } else if (obj instanceof Date && GpxConstants.PT_TIME.equals(key)) {
+            } else if (obj instanceof Instant && GpxConstants.PT_TIME.equals(key)) {
                 // timestamps should always be converted
-                Date date = (Date) obj;
+                Instant date = (Instant) obj;
                 if (!none) { //... but the tag will only be set when required
-                    p.put(GpxConstants.GPX_PREFIX + key, DateUtils.fromDate(date));
-                }
-                p.setTimestamp(date);
+                    p.put(GpxConstants.GPX_PREFIX + key, String.valueOf(date));
+                }
+                p.setTimestamp(Date.from(date));
             }
         }
Index: trunk/src/org/openstreetmap/josm/gui/layer/gpx/DateFilterPanel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/gpx/DateFilterPanel.java	(revision 17714)
+++ trunk/src/org/openstreetmap/josm/gui/layer/gpx/DateFilterPanel.java	(revision 17715)
@@ -7,4 +7,5 @@
 import java.awt.GridBagLayout;
 import java.awt.event.ActionListener;
+import java.time.Instant;
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
@@ -52,7 +53,12 @@
 
         final Date startTime, endTime;
-        Date[] bounds = layer.data.getMinMaxTimeForAllTracks();
-        startTime = (bounds.length == 0) ? Date.from(ZonedDateTime.of(2000, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()).toInstant()) : bounds[0];
-        endTime = (bounds.length == 0) ? new Date() : bounds[1];
+        Instant[] bounds = layer.data.getMinMaxTimeForAllTracks();
+        if (bounds.length == 0) {
+            startTime = Date.from(ZonedDateTime.of(2000, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()).toInstant());
+            endTime = new Date();
+        } else {
+            startTime = Date.from(bounds[0]);
+            endTime = Date.from(bounds[1]);
+        }
 
         dateFrom.setDate(startTime);
Index: trunk/src/org/openstreetmap/josm/gui/layer/gpx/GpxDrawHelper.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/gpx/GpxDrawHelper.java	(revision 17714)
+++ trunk/src/org/openstreetmap/josm/gui/layer/gpx/GpxDrawHelper.java	(revision 17715)
@@ -22,8 +22,8 @@
 import java.io.BufferedReader;
 import java.io.IOException;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.Date;
 import java.util.LinkedList;
 import java.util.List;
@@ -565,8 +565,8 @@
         double now = System.currentTimeMillis()/1000.0;
         if (colored == ColorMode.TIME) {
-            Date[] bounds = data.getMinMaxTimeForAllTracks();
+            Instant[] bounds = data.getMinMaxTimeForAllTracks();
             if (bounds.length >= 2) {
-                minval = bounds[0].getTime()/1000.0;
-                maxval = bounds[1].getTime()/1000.0;
+                minval = bounds[0].getEpochSecond();
+                maxval = bounds[1].getEpochSecond();
             } else {
                 minval = 0;
Index: trunk/src/org/openstreetmap/josm/io/nmea/NmeaReader.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/nmea/NmeaReader.java	(revision 17714)
+++ trunk/src/org/openstreetmap/josm/io/nmea/NmeaReader.java	(revision 17715)
@@ -366,5 +366,5 @@
                     // As this sentence has no complete time only use it
                     // if there is no time so far
-                    currentwp.setTime(d);
+                    currentwp.setInstant(d.toInstant());
                 }
                 // elevation
@@ -494,5 +494,5 @@
                 }
                 // time: this sentence has complete time so always use it.
-                currentwp.setTime(d);
+                currentwp.setInstant(d.toInstant());
                 // speed
                 accu = e[RMC.SPEED.position];
@@ -544,5 +544,5 @@
             if (ps.pWp != currentwp) {
                 if (ps.pWp != null) {
-                    ps.pWp.getDate();
+                    ps.pWp.getInstant();
                 }
                 ps.pWp = currentwp;
Index: trunk/src/org/openstreetmap/josm/io/rtklib/RtkLibPosReader.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/rtklib/RtkLibPosReader.java	(revision 17714)
+++ trunk/src/org/openstreetmap/josm/io/rtklib/RtkLibPosReader.java	(revision 17715)
@@ -91,5 +91,5 @@
                                     Double.parseDouble(fields[IDX_LON])));
                             currentwp.put(GpxConstants.PT_ELE, fields[IDX_HEIGHT]);
-                            currentwp.setTime(parseDate(fields[IDX_DATE]+" "+fields[IDX_TIME]));
+                            currentwp.setInstant(parseDate(fields[IDX_DATE]+" "+fields[IDX_TIME]).toInstant());
                             currentwp.put(GpxConstants.RTKLIB_Q, Integer.parseInt(fields[IDX_Q]));
                             currentwp.put(GpxConstants.PT_SAT, fields[IDX_NS]);
Index: trunk/src/org/openstreetmap/josm/io/session/GeoImageSessionExporter.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/session/GeoImageSessionExporter.java	(revision 17714)
+++ trunk/src/org/openstreetmap/josm/io/session/GeoImageSessionExporter.java	(revision 17715)
@@ -87,5 +87,5 @@
             }
             if (entry.hasGpsTime()) {
-                addAttr("gps-time", Long.toString(entry.getGpsTime().getTime()), imgElem, support);
+                addAttr("gps-time", Long.toString(entry.getGpsInstant().toEpochMilli()), imgElem, support);
             }
             if (entry.getExifOrientation() != null) {
@@ -93,8 +93,8 @@
             }
             if (entry.hasExifTime()) {
-                addAttr("exif-time", Long.toString(entry.getExifTime().getTime()), imgElem, support);
+                addAttr("exif-time", Long.toString(entry.getExifInstant().toEpochMilli()), imgElem, support);
             }
             if (entry.hasExifGpsTime()) {
-                addAttr("exif-gps-time", Long.toString(entry.getExifGpsTime().getTime()), imgElem, support);
+                addAttr("exif-gps-time", Long.toString(entry.getExifGpsInstant().toEpochMilli()), imgElem, support);
             }
             if (entry.getExifCoor() != null) {
Index: trunk/src/org/openstreetmap/josm/io/session/GeoImageSessionImporter.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/session/GeoImageSessionImporter.java	(revision 17714)
+++ trunk/src/org/openstreetmap/josm/io/session/GeoImageSessionImporter.java	(revision 17715)
@@ -6,6 +6,6 @@
 import java.io.File;
 import java.io.IOException;
+import java.time.Instant;
 import java.util.ArrayList;
-import java.util.Date;
 import java.util.List;
 
@@ -90,5 +90,5 @@
                 break;
             case "gps-time":
-                entry.setGpsTime(new Date(Long.parseLong(attrElem.getTextContent())));
+                entry.setGpsTime(Instant.ofEpochMilli(Long.parseLong(attrElem.getTextContent())));
                 break;
             case "exif-orientation":
@@ -96,8 +96,8 @@
                 break;
             case "exif-time":
-                entry.setExifTime(new Date(Long.parseLong(attrElem.getTextContent())));
+                entry.setExifTime(Instant.ofEpochMilli(Long.parseLong(attrElem.getTextContent())));
                 break;
             case "exif-gps-time":
-                entry.setExifGpsTime(new Date(Long.parseLong(attrElem.getTextContent())));
+                entry.setExifGpsTime(Instant.ofEpochMilli(Long.parseLong(attrElem.getTextContent())));
                 break;
             case "exif-coordinates":
Index: trunk/src/org/openstreetmap/josm/tools/ExifReader.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/ExifReader.java	(revision 17714)
+++ trunk/src/org/openstreetmap/josm/tools/ExifReader.java	(revision 17715)
@@ -6,4 +6,5 @@
 import java.io.IOException;
 import java.time.DateTimeException;
+import java.time.Instant;
 import java.util.Date;
 import java.util.List;
@@ -42,9 +43,21 @@
      * @param filename The JPEG file to read
      * @return The date/time read in the EXIF section, or {@code null} if not found
-     */
+     * @deprecated Use {@link #readInstant(File)}
+     */
+    @Deprecated
     public static Date readTime(File filename) {
-        try {
-            final Metadata metadata = JpegMetadataReader.readMetadata(filename);
-            return readTime(metadata);
+        Instant instant = readInstant(filename);
+        return instant == null ? null : Date.from(instant);
+    }
+
+    /**
+     * Returns the date/time from the given JPEG file.
+     * @param filename The JPEG file to read
+     * @return The date/time read in the EXIF section, or {@code null} if not found
+     */
+    public static Instant readInstant(File filename) {
+        try {
+            final Metadata metadata = JpegMetadataReader.readMetadata(filename);
+            return readInstant(metadata);
         } catch (JpegProcessingException | IOException e) {
             Logging.error(e);
@@ -58,6 +71,18 @@
      * @return The date/time read in the EXIF section, or {@code null} if not found
      * @since 11745
-     */
+     * @deprecated Use {@link #readInstant(Metadata)}
+     */
+    @Deprecated
     public static Date readTime(Metadata metadata) {
+        Instant instant = readInstant(metadata);
+        return instant == null ? null : Date.from(instant);
+    }
+
+    /**
+     * Returns the date/time from the given JPEG file.
+     * @param metadata The EXIF metadata
+     * @return The date/time read in the EXIF section, or {@code null} if not found
+     */
+    public static Instant readInstant(Metadata metadata) {
         try {
             String dateTimeOrig = null;
@@ -109,8 +134,8 @@
             if (dateStr != null) {
                 dateStr = dateStr.replace('/', ':'); // workaround for HTC Sensation bug, see #7228
-                final Date date = DateUtils.fromString(dateStr);
+                Instant date = DateUtils.parseInstant(dateStr);
                 if (subSeconds != null) {
                     try {
-                        date.setTime(date.getTime() + (long) (TimeUnit.SECONDS.toMillis(1) * Double.parseDouble("0." + subSeconds)));
+                        date = date.plusMillis((long) (TimeUnit.SECONDS.toMillis(1) * Double.parseDouble("0." + subSeconds)));
                     } catch (NumberFormatException e) {
                         Logging.warn("Failed parsing sub seconds from [{0}]", subSeconds);
Index: trunk/src/org/openstreetmap/josm/tools/date/DateUtils.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/date/DateUtils.java	(revision 17714)
+++ trunk/src/org/openstreetmap/josm/tools/date/DateUtils.java	(revision 17715)
@@ -287,5 +287,7 @@
      * @return the date format used for GPX waypoints
      * @since 14055
-     */
+     * @deprecated Use {@link Instant#toString()}
+     */
+    @Deprecated
     public static DateFormat getGpxFormat() {
         SimpleDateFormat result = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
Index: trunk/test/unit/org/openstreetmap/josm/data/ImageDataTest.java
===================================================================
--- trunk/test/unit/org/openstreetmap/josm/data/ImageDataTest.java	(revision 17714)
+++ trunk/test/unit/org/openstreetmap/josm/data/ImageDataTest.java	(revision 17715)
@@ -8,8 +8,8 @@
 
 import java.io.File;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.Date;
 import java.util.List;
 
@@ -28,5 +28,5 @@
 class ImageDataTest {
 
-    private static ImageEntry newImageEntry(String file, Date exifTime) {
+    private static ImageEntry newImageEntry(String file, Instant exifTime) {
         ImageEntry entry = new ImageEntry(new File(file));
         entry.setExifTime(exifTime);
@@ -65,6 +65,6 @@
     @Test
     void testSortData() {
-        ImageEntry entry1 = newImageEntry("test1", new Date(1_000_000));
-        ImageEntry entry2 = newImageEntry("test2", new Date(2_000_000));
+        ImageEntry entry1 = newImageEntry("test1", Instant.ofEpochMilli(1_000_000));
+        ImageEntry entry2 = newImageEntry("test2", Instant.ofEpochMilli(2_000_000));
 
         ArrayList<ImageEntry> list = new ArrayList<>();
Index: trunk/test/unit/org/openstreetmap/josm/data/gpx/GpxDataTest.java
===================================================================
--- trunk/test/unit/org/openstreetmap/josm/data/gpx/GpxDataTest.java	(revision 17714)
+++ trunk/test/unit/org/openstreetmap/josm/data/gpx/GpxDataTest.java	(revision 17715)
@@ -10,4 +10,5 @@
 
 import java.io.IOException;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -337,14 +338,14 @@
         WayPoint p4 = new WayPoint(LatLon.NORTH_POLE);
         WayPoint p5 = new WayPoint(LatLon.NORTH_POLE);
-        p1.setTime(new Date(200020));
-        p2.setTime(new Date(100020));
-        p4.setTime(new Date(500020));
+        p1.setInstant(new Date(200020).toInstant());
+        p2.setInstant(new Date(100020).toInstant());
+        p4.setInstant(new Date(500020).toInstant());
         data.addTrack(new GpxTrack(Arrays.asList(Arrays.asList(p1, p2)), Collections.emptyMap()));
         data.addTrack(new GpxTrack(Arrays.asList(Arrays.asList(p3, p4, p5)), Collections.emptyMap()));
 
-        Date[] times = data.getMinMaxTimeForAllTracks();
+        Instant[] times = data.getMinMaxTimeForAllTracks();
         assertEquals(times.length, 2);
-        assertEquals(new Date(100020), times[0]);
-        assertEquals(new Date(500020), times[1]);
+        assertEquals(Instant.ofEpochMilli(100020), times[0]);
+        assertEquals(Instant.ofEpochMilli(500020), times[1]);
     }
 
Index: trunk/test/unit/org/openstreetmap/josm/data/gpx/GpxImageCorrelationTest.java
===================================================================
--- trunk/test/unit/org/openstreetmap/josm/data/gpx/GpxImageCorrelationTest.java	(revision 17714)
+++ trunk/test/unit/org/openstreetmap/josm/data/gpx/GpxImageCorrelationTest.java	(revision 17715)
@@ -58,26 +58,26 @@
 
         final GpxImageEntry ib = new GpxImageEntry();
-        ib.setExifTime(DateUtils.fromString("2016:01:03 11:54:58")); // 5 minutes before start of GPX
+        ib.setExifTime(DateUtils.parseInstant("2016:01:03 11:54:58")); // 5 minutes before start of GPX
         ib.createTmp();
         final GpxImageEntry i0 = new GpxImageEntry();
-        i0.setExifTime(DateUtils.fromString("2016:01:03 11:59:54")); // 4 sec before start of GPX
+        i0.setExifTime(DateUtils.parseInstant("2016:01:03 11:59:54")); // 4 sec before start of GPX
         i0.createTmp();
         final GpxImageEntry i1 = new GpxImageEntry();
-        i1.setExifTime(DateUtils.fromString("2016:01:03 12:04:01"));
+        i1.setExifTime(DateUtils.parseInstant("2016:01:03 12:04:01"));
         i1.createTmp();
         final GpxImageEntry i2 = new GpxImageEntry();
-        i2.setExifTime(DateUtils.fromString("2016:01:03 12:04:57"));
+        i2.setExifTime(DateUtils.parseInstant("2016:01:03 12:04:57"));
         i2.createTmp();
         final GpxImageEntry i3 = new GpxImageEntry();
-        i3.setExifTime(DateUtils.fromString("2016:01:03 12:05:05"));
+        i3.setExifTime(DateUtils.parseInstant("2016:01:03 12:05:05"));
         i3.createTmp();
         final GpxImageEntry i4 = new GpxImageEntry(); //Image close to two points with elevation, but without time
-        i4.setExifTime(DateUtils.fromString("2016:01:03 12:05:20"));
+        i4.setExifTime(DateUtils.parseInstant("2016:01:03 12:05:20"));
         i4.createTmp();
         final GpxImageEntry i5 = new GpxImageEntry(); //between two tracks, closer to first
-        i5.setExifTime(DateUtils.fromString("2016:01:03 12:07:00"));
+        i5.setExifTime(DateUtils.parseInstant("2016:01:03 12:07:00"));
         i5.createTmp();
         final GpxImageEntry i6 = new GpxImageEntry(); //between two tracks, closer to second (more than 1 minute from any track)
-        i6.setExifTime(DateUtils.fromString("2016:01:03 12:07:45"));
+        i6.setExifTime(DateUtils.parseInstant("2016:01:03 12:07:45"));
         i6.createTmp();
 
@@ -119,9 +119,9 @@
         assertEquals(null, i6.getElevation());
 
-        assertEquals(null, ib.getGpsTime());
-        assertEquals(DateUtils.fromString("2016:01:03 11:59:54"), i0.getGpsTime()); // original time is kept
-        assertEquals(DateUtils.fromString("2016:01:03 12:04:01"), i1.getGpsTime());
-        assertEquals(DateUtils.fromString("2016:01:03 12:04:57"), i2.getGpsTime());
-        assertEquals(DateUtils.fromString("2016:01:03 12:05:05"), i3.getGpsTime());
+        assertEquals(null, ib.getGpsInstant());
+        assertEquals(DateUtils.parseInstant("2016:01:03 11:59:54"), i0.getGpsInstant()); // original time is kept
+        assertEquals(DateUtils.parseInstant("2016:01:03 12:04:01"), i1.getGpsInstant());
+        assertEquals(DateUtils.parseInstant("2016:01:03 12:04:57"), i2.getGpsInstant());
+        assertEquals(DateUtils.parseInstant("2016:01:03 12:05:05"), i3.getGpsInstant());
 
         clearTmp(images);
Index: trunk/test/unit/org/openstreetmap/josm/data/gpx/WayPointTest.java
===================================================================
--- trunk/test/unit/org/openstreetmap/josm/data/gpx/WayPointTest.java	(revision 17714)
+++ trunk/test/unit/org/openstreetmap/josm/data/gpx/WayPointTest.java	(revision 17715)
@@ -5,4 +5,5 @@
 import org.junit.jupiter.api.Test;
 import org.openstreetmap.josm.TestUtils;
+import org.openstreetmap.josm.data.coor.LatLon;
 import org.openstreetmap.josm.testutils.JOSMTestRules;
 
@@ -10,4 +11,9 @@
 import nl.jqno.equalsverifier.EqualsVerifier;
 import nl.jqno.equalsverifier.Warning;
+
+import java.time.Instant;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
 
 /**
@@ -37,3 +43,18 @@
             .verify();
     }
+
+    /**
+     * Unit test of copy constructor {@link WayPoint#WayPoint(WayPoint)}
+     */
+    @Test
+    void testConstructor() {
+        WayPoint wp1 = new WayPoint(new LatLon(12., 34.));
+        wp1.setInstant(Instant.ofEpochMilli(123_456_789));
+        WayPoint wp2 = new WayPoint(wp1);
+        assertEquals(wp1, wp2);
+        assertEquals(wp1.getInstant(), wp2.getInstant());
+        wp2.setInstant(Instant.ofEpochMilli(234_456_789));
+        assertNotEquals(wp1, wp2);
+        assertNotEquals(wp1.getInstant(), wp2.getInstant());
+    }
 }
Index: trunk/test/unit/org/openstreetmap/josm/gui/layer/OsmDataLayerTest.java
===================================================================
--- trunk/test/unit/org/openstreetmap/josm/gui/layer/OsmDataLayerTest.java	(revision 17714)
+++ trunk/test/unit/org/openstreetmap/josm/gui/layer/OsmDataLayerTest.java	(revision 17715)
@@ -10,5 +10,4 @@
 import java.io.File;
 import java.nio.charset.StandardCharsets;
-import java.text.DateFormat;
 import java.util.Collection;
 import java.util.Collections;
@@ -39,5 +38,4 @@
 import org.openstreetmap.josm.testutils.mockers.ExtendedDialogMocker;
 import org.openstreetmap.josm.tools.Logging;
-import org.openstreetmap.josm.tools.date.DateUtils;
 
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
@@ -245,17 +243,16 @@
         assertEquals(3, trackpoints.size());
         Iterator<WayPoint> it = trackpoints.iterator();
-        DateFormat gpxFormat = DateUtils.getGpxFormat();
         WayPoint p1 = it.next();
         assertEquals(new LatLon(47.0, 9.0), p1.getCoor());
         assertEquals("123", p1.get(GpxConstants.PT_ELE));
-        assertEquals("2018-08-01T10:00:00.000Z", gpxFormat.format(p1.get(GpxConstants.PT_TIME)));
+        assertEquals("2018-08-01T10:00:00Z", String.valueOf(p1.get(GpxConstants.PT_TIME)));
         WayPoint p2 = it.next();
         assertEquals(new LatLon(47.1, 9.1), p2.getCoor());
         assertEquals("456", p2.get(GpxConstants.PT_ELE));
-        assertEquals("2018-08-01T10:01:00.000Z", gpxFormat.format(p2.get(GpxConstants.PT_TIME)));
+        assertEquals("2018-08-01T10:01:00Z", String.valueOf(p2.get(GpxConstants.PT_TIME)));
         WayPoint p3 = it.next();
         assertEquals(new LatLon(47.05, 9.05), p3.getCoor());
         assertEquals("789", p3.get(GpxConstants.PT_ELE));
-        assertEquals("2018-08-01T10:02:00.000Z", gpxFormat.format(p3.get(GpxConstants.PT_TIME)));
+        assertEquals("2018-08-01T10:02:00Z", String.valueOf(p3.get(GpxConstants.PT_TIME)));
     }
 
Index: trunk/test/unit/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImagesTest.java
===================================================================
--- trunk/test/unit/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImagesTest.java	(revision 17714)
+++ trunk/test/unit/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImagesTest.java	(revision 17715)
@@ -48,5 +48,5 @@
         final GpxData gpx = GpxReaderTest.parseGpxData("nodist/data/2094047.gpx");
         final ImageEntry i0 = new ImageEntry();
-        i0.setExifTime(DateUtils.fromString("2016:01:03 11:59:54")); // 4 sec before start of GPX
+        i0.setExifTime(DateUtils.parseInstant("2016:01:03 11:59:54")); // 4 sec before start of GPX
         i0.createTmp();
         assertEquals(Pair.create(GpxTimezone.ZERO, GpxTimeOffset.seconds(-4)),
Index: trunk/test/unit/org/openstreetmap/josm/gui/layer/geoimage/ImageEntryTest.java
===================================================================
--- trunk/test/unit/org/openstreetmap/josm/gui/layer/geoimage/ImageEntryTest.java	(revision 17714)
+++ trunk/test/unit/org/openstreetmap/josm/gui/layer/geoimage/ImageEntryTest.java	(revision 17715)
@@ -27,5 +27,5 @@
         ImageEntry e = new ImageEntry(new File(TestUtils.getRegressionDataFile(12255, "G0016941.JPG")));
         e.extractExif();
-        assertNotNull(e.getExifTime());
+        assertNotNull(e.getExifInstant());
     }
 
Index: trunk/test/unit/org/openstreetmap/josm/io/nmea/NmeaReaderTest.java
===================================================================
--- trunk/test/unit/org/openstreetmap/josm/io/nmea/NmeaReaderTest.java	(revision 17714)
+++ trunk/test/unit/org/openstreetmap/josm/io/nmea/NmeaReaderTest.java	(revision 17715)
@@ -11,11 +11,9 @@
 import java.nio.file.Files;
 import java.nio.file.Paths;
-import java.text.SimpleDateFormat;
+import java.time.Instant;
 import java.util.ArrayList;
-import java.util.Date;
 import java.util.List;
 import java.util.TimeZone;
 
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
@@ -45,14 +43,4 @@
     public JOSMTestRules test = new JOSMTestRules();
 
-    private final SimpleDateFormat iso8601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX");
-
-    /**
-     * Forces the timezone.
-     */
-    @BeforeEach
-    public void setUp() {
-        iso8601.setTimeZone(DateUtils.UTC);
-    }
-
     /**
      * Tests reading a nmea file.
@@ -68,12 +56,12 @@
 
         final List<WayPoint> wayPoints = new ArrayList<>(in.data.tracks.iterator().next().getSegments().iterator().next().getWayPoints());
-        assertEquals(DateUtils.fromString("2016-01-25T05:05:09.200Z"), wayPoints.get(0).get(GpxConstants.PT_TIME));
-        assertEquals(DateUtils.fromString("2016-01-25T05:05:09.400Z"), wayPoints.get(1).get(GpxConstants.PT_TIME));
-        assertEquals(DateUtils.fromString("2016-01-25T05:05:09.600Z"), wayPoints.get(2).get(GpxConstants.PT_TIME));
-        assertEquals(wayPoints.get(0).getDate(), wayPoints.get(0).get(GpxConstants.PT_TIME));
+        assertEquals(DateUtils.parseInstant("2016-01-25T05:05:09.200Z"), wayPoints.get(0).get(GpxConstants.PT_TIME));
+        assertEquals(DateUtils.parseInstant("2016-01-25T05:05:09.400Z"), wayPoints.get(1).get(GpxConstants.PT_TIME));
+        assertEquals(DateUtils.parseInstant("2016-01-25T05:05:09.600Z"), wayPoints.get(2).get(GpxConstants.PT_TIME));
+        assertEquals(wayPoints.get(0).getInstant(), wayPoints.get(0).get(GpxConstants.PT_TIME));
 
-        assertEquals("2016-01-25T05:05:09.200Z", iso8601.format(wayPoints.get(0).getDate()));
-        assertEquals("2016-01-25T05:05:09.400Z", iso8601.format(wayPoints.get(1).getDate()));
-        assertEquals("2016-01-25T05:05:09.600Z", iso8601.format(wayPoints.get(2).getDate()));
+        assertEquals(DateUtils.parseInstant("2016-01-25T05:05:09.200Z"), wayPoints.get(0).getInstant());
+        assertEquals(DateUtils.parseInstant("2016-01-25T05:05:09.400Z"), wayPoints.get(1).getInstant());
+        assertEquals(DateUtils.parseInstant("2016-01-25T05:05:09.600Z"), wayPoints.get(2).getInstant());
 
         assertEquals(new LatLon(46.98807, -1.400525), wayPoints.get(0).getCoor());
@@ -171,6 +159,6 @@
     }
 
-    private static Date readDate(String nmeaLine) throws IOException, SAXException {
-        return readWayPoint(nmeaLine).getDate();
+    private static Instant readDate(String nmeaLine) throws IOException, SAXException {
+        return readWayPoint(nmeaLine).getInstant();
     }
 
@@ -185,10 +173,10 @@
     @Test
     void testTicket16496() throws Exception {
-        assertEquals("2018-05-30T16:28:59.400Z", iso8601.format(
-                readDate("$GNRMC,162859.400,A,4543.03388,N,00058.19870,W,45.252,209.07,300518,,,D,V*13")));
-        assertEquals("2018-05-30T16:28:59.400Z", iso8601.format(
-                readDate("$GNRMC,162859.40,A,4543.03388,N,00058.19870,W,45.252,209.07,300518,,,D,V*23")));
-        assertEquals("2018-05-30T16:28:59.400Z", iso8601.format(
-                readDate("$GNRMC,162859.4,A,4543.03388,N,00058.19870,W,45.252,209.07,300518,,,D,V*13")));
+        assertEquals(DateUtils.parseInstant("2018-05-30T16:28:59.400Z"),
+                readDate("$GNRMC,162859.400,A,4543.03388,N,00058.19870,W,45.252,209.07,300518,,,D,V*13"));
+        assertEquals(DateUtils.parseInstant("2018-05-30T16:28:59.400Z"),
+                readDate("$GNRMC,162859.40,A,4543.03388,N,00058.19870,W,45.252,209.07,300518,,,D,V*23"));
+        assertEquals(DateUtils.parseInstant("2018-05-30T16:28:59.400Z"),
+                readDate("$GNRMC,162859.4,A,4543.03388,N,00058.19870,W,45.252,209.07,300518,,,D,V*13"));
     }
 
Index: trunk/test/unit/org/openstreetmap/josm/io/rtklib/RtkLibPosReaderTest.java
===================================================================
--- trunk/test/unit/org/openstreetmap/josm/io/rtklib/RtkLibPosReaderTest.java	(revision 17714)
+++ trunk/test/unit/org/openstreetmap/josm/io/rtklib/RtkLibPosReaderTest.java	(revision 17715)
@@ -7,10 +7,9 @@
 import java.nio.file.Files;
 import java.nio.file.Paths;
-import java.text.SimpleDateFormat;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.TimeZone;
 
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
@@ -35,14 +34,4 @@
     public JOSMTestRules test = new JOSMTestRules();
 
-    private final SimpleDateFormat iso8601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX");
-
-    /**
-     * Forces the timezone.
-     */
-    @BeforeEach
-    public void setUp() {
-        iso8601.setTimeZone(DateUtils.UTC);
-    }
-
     private static RtkLibPosReader read(String path) throws IOException, SAXException {
         TimeZone.setDefault(TimeZone.getTimeZone("Europe/Berlin"));
@@ -62,12 +51,12 @@
 
         List<WayPoint> wayPoints = new ArrayList<>(in.getGpxData().tracks.iterator().next().getSegments().iterator().next().getWayPoints());
-        assertEquals(DateUtils.fromString("2019-06-08T08:23:12.000Z"), wayPoints.get(0).get(GpxConstants.PT_TIME));
-        assertEquals(DateUtils.fromString("2019-06-08T08:23:12.300Z"), wayPoints.get(1).get(GpxConstants.PT_TIME));
-        assertEquals(DateUtils.fromString("2019-06-08T08:23:12.600Z"), wayPoints.get(2).get(GpxConstants.PT_TIME));
-        assertEquals(wayPoints.get(0).getDate(), wayPoints.get(0).get(GpxConstants.PT_TIME));
+        assertEquals(DateUtils.parseInstant("2019-06-08T08:23:12.000Z"), wayPoints.get(0).get(GpxConstants.PT_TIME));
+        assertEquals(DateUtils.parseInstant("2019-06-08T08:23:12.300Z"), wayPoints.get(1).get(GpxConstants.PT_TIME));
+        assertEquals(DateUtils.parseInstant("2019-06-08T08:23:12.600Z"), wayPoints.get(2).get(GpxConstants.PT_TIME));
+        assertEquals(wayPoints.get(0).getInstant(), wayPoints.get(0).get(GpxConstants.PT_TIME));
 
-        assertEquals("2019-06-08T08:23:12.000Z", iso8601.format(wayPoints.get(0).getDate()));
-        assertEquals("2019-06-08T08:23:12.300Z", iso8601.format(wayPoints.get(1).getDate()));
-        assertEquals("2019-06-08T08:23:12.600Z", iso8601.format(wayPoints.get(2).getDate()));
+        assertEquals(Instant.parse("2019-06-08T08:23:12.000Z"), wayPoints.get(0).getInstant());
+        assertEquals(Instant.parse("2019-06-08T08:23:12.300Z"), wayPoints.get(1).getInstant());
+        assertEquals(Instant.parse("2019-06-08T08:23:12.600Z"), wayPoints.get(2).getInstant());
 
         assertEquals(new LatLon(46.948881673, -1.484757046), wayPoints.get(0).getCoor());
Index: trunk/test/unit/org/openstreetmap/josm/tools/ExifReaderTest.java
===================================================================
--- trunk/test/unit/org/openstreetmap/josm/tools/ExifReaderTest.java	(revision 17714)
+++ trunk/test/unit/org/openstreetmap/josm/tools/ExifReaderTest.java	(revision 17715)
@@ -8,7 +8,5 @@
 import java.io.IOException;
 import java.text.DecimalFormat;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
+import java.time.Instant;
 
 import org.junit.jupiter.api.BeforeEach;
@@ -48,28 +46,23 @@
     /**
      * Test time extraction
-     * @throws ParseException if {@link ExifReader#readTime} fails to parse date/time of sample file
      */
     @Test
-    void testReadTime() throws ParseException {
-        Date date = ExifReader.readTime(directionSampleFile);
-        doTest("2010-05-15T17:12:05.000", date);
+    void testReadTime() {
+        Instant date = ExifReader.readInstant(directionSampleFile);
+        assertEquals(Instant.parse("2010-05-15T17:12:05.000Z"), date);
     }
 
     /**
      * Tests reading sub-seconds from the EXIF header
-     * @throws ParseException if {@link ExifReader#readTime} fails to parse date/time of sample file
      */
     @Test
-    void testReadTimeSubSecond1() throws ParseException {
-        Date date = ExifReader.readTime(new File("nodist/data/IMG_20150711_193419.jpg"));
-        doTest("2015-07-11T19:34:19.100", date);
-    }
-
-    private static void doTest(String expectedDate, Date parsedDate) {
-        assertEquals(expectedDate, new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS").format(parsedDate));
+    void testReadTimeSubSecond1() {
+        Instant date = ExifReader.readInstant(new File("nodist/data/IMG_20150711_193419.jpg"));
+        assertEquals(Instant.parse("2015-07-11T19:34:19.100Z"), date);
     }
 
     private static void doTestFile(String expectedDate, int ticket, String filename) {
-        doTest(expectedDate, ExifReader.readTime(new File(TestUtils.getRegressionDataFile(ticket, filename))));
+        Instant date = ExifReader.readInstant(new File(TestUtils.getRegressionDataFile(ticket, filename)));
+        assertEquals(Instant.parse(expectedDate), date);
     }
 
@@ -125,5 +118,5 @@
     @Test
     void testTicket11685() throws IOException {
-        doTestFile("2015-11-08T15:33:27.500", 11685, "2015-11-08_15-33-27-Xiaomi_YI-Y0030832.jpg");
+        doTestFile("2015-11-08T15:33:27.500Z", 11685, "2015-11-08_15-33-27-Xiaomi_YI-Y0030832.jpg");
     }
 
@@ -134,6 +127,6 @@
     @Test
     void testTicket14209() throws IOException {
-        doTestFile("2017-01-16T18:27:00.000", 14209, "0MbEfj1S--.1.jpg");
-        doTestFile("2016-08-13T19:51:13.000", 14209, "7VWFOryj--.1.jpg");
+        doTestFile("2017-01-16T18:27:00.000Z", 14209, "0MbEfj1S--.1.jpg");
+        doTestFile("2016-08-13T19:51:13.000Z", 14209, "7VWFOryj--.1.jpg");
     }
 }
