Index: /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java	(revision 9740)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java	(revision 9741)
@@ -35,4 +35,5 @@
 import java.util.Hashtable;
 import java.util.List;
+import java.util.Objects;
 import java.util.TimeZone;
 import java.util.zip.GZIPInputStream;
@@ -94,6 +95,6 @@
 
     private final transient GeoImageLayer yLayer;
-    private double timezone;
-    private long delta;
+    private Timezone timezone;
+    private Offset delta;
 
     /**
@@ -122,5 +123,5 @@
             // Parse values again, to display an error if the format is not recognized
             try {
-                timezone = parseTimezone(tfTimezone.getText().trim());
+                timezone = Timezone.parseTimezone(tfTimezone.getText().trim());
             } catch (ParseException e) {
                 JOptionPane.showMessageDialog(Main.parent, e.getMessage(),
@@ -130,5 +131,5 @@
 
             try {
-                delta = parseOffset(tfOffset.getText().trim());
+                delta = Offset.parseOffset(tfOffset.getText().trim());
             } catch (ParseException e) {
                 JOptionPane.showMessageDialog(Main.parent, e.getMessage(),
@@ -168,6 +169,6 @@
                 break;
             case DONE:
-                Main.pref.put("geoimage.timezone", formatTimezone(timezone));
-                Main.pref.put("geoimage.delta", Long.toString(delta * 1000));
+                Main.pref.put("geoimage.timezone", timezone.formatTimezone());
+                Main.pref.put("geoimage.delta", delta.formatOffset());
                 Main.pref.put("geoimage.showThumbs", yLayer.useThumbs);
 
@@ -410,5 +411,5 @@
 
                 String tzDesc = new StringBuilder(tzStr).append(" (")
-                .append(formatTimezone(tz.getRawOffset() / 3600000.0))
+                .append(new Timezone(tz.getRawOffset() / 3600000.0).formatTimezone())
                 .append(')').toString();
                 vtTimezones.add(tzDesc);
@@ -428,5 +429,5 @@
 
             cbTimezones.setSelectedItem(new StringBuilder(defaultTz.getID()).append(" (")
-                    .append(formatTimezone(defaultTz.getRawOffset() / 3600000.0))
+                    .append(new Timezone(defaultTz.getRawOffset() / 3600000.0).formatTimezone())
                     .append(')').toString());
 
@@ -551,5 +552,5 @@
 
                 Main.pref.put("geoimage.timezoneid", tzId);
-                tfOffset.setText(Long.toString(delta / 1000));
+                tfOffset.setText(Offset.milliseconds(delta).formatOffset());
                 tfTimezone.setText(tzValue);
 
@@ -609,21 +610,20 @@
         }
         try {
-            timezone = parseTimezone(prefTimezone);
+            timezone = Timezone.parseTimezone(prefTimezone);
         } catch (ParseException e) {
-            timezone = 0;
+            timezone = Timezone.ZERO;
         }
 
         tfTimezone = new JosmTextField(10);
-        tfTimezone.setText(formatTimezone(timezone));
+        tfTimezone.setText(timezone.formatTimezone());
 
         try {
-            delta = parseOffset(Main.pref.get("geoimage.delta", "0"));
+            delta = Offset.parseOffset(Main.pref.get("geoimage.delta", "0"));
         } catch (ParseException e) {
-            delta = 0;
-        }
-        delta = delta / 1000;  // milliseconds -> seconds
+            delta = Offset.ZERO;
+        }
 
         tfOffset = new JosmTextField(10);
-        tfOffset.setText(Long.toString(delta));
+        tfOffset.setText(delta.formatOffset());
 
         JButton buttonViewGpsPhoto = new JButton(tr("<html>Use photo of an accurate clock,<br>"
@@ -813,6 +813,6 @@
         private String statusText() {
             try {
-                timezone = parseTimezone(tfTimezone.getText().trim());
-                delta = parseOffset(tfOffset.getText().trim());
+                timezone = Timezone.parseTimezone(tfTimezone.getText().trim());
+                delta = Offset.parseOffset(tfOffset.getText().trim());
             } catch (ParseException e) {
                 return e.getMessage();
@@ -839,5 +839,5 @@
                 return tr("No gpx selected");
 
-            final long offset_ms = ((long) (timezone * 3600) + delta) * 1000; // in milliseconds
+            final long offset_ms = ((long) (timezone.getHours() * 3600 * 1000)) + delta.getMilliseconds(); // in milliseconds
             lastNumMatched = matchGpxTrack(dateImgLst, selGpx.data, offset_ms);
 
@@ -869,5 +869,5 @@
         public void actionPerformed(ActionEvent arg0) {
 
-            long diff = delta + Math.round(timezone*60*60);
+            long diff = delta.getSeconds() + Math.round(timezone.getHours() * 60 * 60);
 
             double diffInH = (double) diff/(60*60);    // hours
@@ -934,15 +934,15 @@
 
                     try {
-                        timezone = parseTimezone(zone);
+                        timezone = Timezone.parseTimezone(zone);
                     } catch (ParseException pe) {
                         throw new RuntimeException(pe);
                     }
-                    delta = sldMinutes.getValue()*60 + sldSeconds.getValue();
+                    delta = Offset.seconds(sldMinutes.getValue() * 60 + sldSeconds.getValue() + 24 * 60 * 60L * dayOffset); // add the day offset
 
                     tfTimezone.getDocument().removeDocumentListener(statusBarUpdater);
                     tfOffset.getDocument().removeDocumentListener(statusBarUpdater);
 
-                    tfTimezone.setText(formatTimezone(timezone));
-                    tfOffset.setText(Long.toString(delta + 24*60*60L*dayOffset));    // add the day offset to the offset field
+                    tfTimezone.setText(timezone.formatTimezone());
+                    tfOffset.setText(delta.formatOffset());
 
                     tfTimezone.getDocument().addDocumentListener(statusBarUpdater);
@@ -1009,9 +1009,9 @@
      * @param imgs the images to correlate
      * @param gpx the gpx track to correlate to
-     * @return a pair of timezone (in hours) and offset (in seconds)
+     * @return a pair of timezone and offset
      * @throws IndexOutOfBoundsException when there are no images
      * @throws NoGpxTimestamps when the gpx track does not contain a timestamp
      */
-    static Pair<Double, Long> autoGuess(List<ImageEntry> imgs, GpxData gpx) throws IndexOutOfBoundsException, NoGpxTimestamps {
+    static Pair<Timezone, Offset> autoGuess(List<ImageEntry> imgs, GpxData gpx) throws IndexOutOfBoundsException, NoGpxTimestamps {
 
         // Init variables
@@ -1056,5 +1056,5 @@
         final double timezone = (double) Math.round(tz * 2) / 2; // hours, rounded to one decimal place
         final long delta = Math.round(diff - timezone * 60 * 60); // seconds
-        return Pair.create(timezone, delta);
+        return Pair.create(new Timezone(timezone), Offset.seconds(delta));
     }
 
@@ -1071,5 +1071,5 @@
 
             try {
-                final Pair<Double, Long> r = autoGuess(imgs, gpx);
+                final Pair<Timezone, Offset> r = autoGuess(imgs, gpx);
                 timezone = r.a;
                 delta = r.b;
@@ -1089,6 +1089,6 @@
             tfOffset.getDocument().removeDocumentListener(statusBarUpdater);
 
-            tfTimezone.setText(formatTimezone(timezone));
-            tfOffset.setText(Long.toString(delta));
+            tfTimezone.setText(timezone.formatTimezone());
+            tfOffset.setText(delta.formatOffset());
             tfOffset.requestFocus();
 
@@ -1318,108 +1318,187 @@
     }
 
-    static String formatTimezone(double timezone) {
-        StringBuilder ret = new StringBuilder();
-
-        if (timezone < 0) {
-            ret.append('-');
-            timezone = -timezone;
-        } else {
-            ret.append('+');
-        }
-        ret.append((long) timezone).append(':');
-        int minutes = (int) ((timezone % 1) * 60);
-        if (minutes < 10) {
-            ret.append('0');
-        }
-        ret.append(minutes);
-
-        return ret.toString();
-    }
-
-    static double parseTimezone(String timezone) throws ParseException {
-
-        if (timezone.isEmpty())
-            return 0;
-
-        String error = tr("Error while parsing timezone.\nExpected format: {0}", "+H:MM");
-
-        char sgnTimezone = '+';
-        StringBuilder hTimezone = new StringBuilder();
-        StringBuilder mTimezone = new StringBuilder();
-        int state = 1; // 1=start/sign, 2=hours, 3=minutes.
-        for (int i = 0; i < timezone.length(); i++) {
-            char c = timezone.charAt(i);
-            switch (c) {
-            case ' ' :
-                if (state != 2 || hTimezone.length() != 0)
-                    throw new ParseException(error, i);
-                break;
-            case '+' :
-            case '-' :
-                if (state == 1) {
-                    sgnTimezone = c;
-                    state = 2;
-                } else
-                    throw new ParseException(error, i);
-                break;
-            case ':' :
-            case '.' :
-                if (state == 2) {
-                    state = 3;
-                } else
-                    throw new ParseException(error, i);
-                break;
-            case '0' : case '1' : case '2' : case '3' : case '4' :
-            case '5' : case '6' : case '7' : case '8' : case '9' :
-                switch(state) {
-                case 1 :
-                case 2 :
-                    state = 2;
-                    hTimezone.append(c);
-                    break;
-                case 3 :
-                    mTimezone.append(c);
-                    break;
-                default :
-                    throw new ParseException(error, i);
-                }
-                break;
-            default :
-                throw new ParseException(error, i);
-            }
-        }
-
-        int h = 0;
-        int m = 0;
-        try {
-            h = Integer.parseInt(hTimezone.toString());
-            if (mTimezone.length() > 0) {
-                m = Integer.parseInt(mTimezone.toString());
-            }
-        } catch (NumberFormatException nfe) {
-            // Invalid timezone
-            throw new ParseException(error, 0);
-        }
-
-        if (h > 12 || m > 59)
-            throw new ParseException(error, 0);
-        else
-            return (h + m / 60.0) * (sgnTimezone == '-' ? -1 : 1);
-    }
-
-    static long parseOffset(String offset) throws ParseException {
-        String error = tr("Error while parsing offset.\nExpected format: {0}", "number");
-
-        if (!offset.isEmpty()) {
+    static final class Timezone {
+
+        static final Timezone ZERO = new Timezone(0.0);
+        private final double timezone;
+
+        Timezone(double hours) {
+            this.timezone = hours;
+        }
+
+        public double getHours() {
+            return timezone;
+        }
+
+        String formatTimezone() {
+            StringBuilder ret = new StringBuilder();
+
+            double timezone = this.timezone;
+            if (timezone < 0) {
+                ret.append('-');
+                timezone = -timezone;
+            } else {
+                ret.append('+');
+            }
+            ret.append((long) timezone).append(':');
+            int minutes = (int) ((timezone % 1) * 60);
+            if (minutes < 10) {
+                ret.append('0');
+            }
+            ret.append(minutes);
+
+            return ret.toString();
+        }
+
+        static Timezone parseTimezone(String timezone) throws ParseException {
+
+            if (timezone.isEmpty())
+                return ZERO;
+
+            String error = tr("Error while parsing timezone.\nExpected format: {0}", "+H:MM");
+
+            char sgnTimezone = '+';
+            StringBuilder hTimezone = new StringBuilder();
+            StringBuilder mTimezone = new StringBuilder();
+            int state = 1; // 1=start/sign, 2=hours, 3=minutes.
+            for (int i = 0; i < timezone.length(); i++) {
+                char c = timezone.charAt(i);
+                switch (c) {
+                    case ' ':
+                        if (state != 2 || hTimezone.length() != 0)
+                            throw new ParseException(error, i);
+                        break;
+                    case '+':
+                    case '-':
+                        if (state == 1) {
+                            sgnTimezone = c;
+                            state = 2;
+                        } else
+                            throw new ParseException(error, i);
+                        break;
+                    case ':':
+                    case '.':
+                        if (state == 2) {
+                            state = 3;
+                        } else
+                            throw new ParseException(error, i);
+                        break;
+                    case '0':
+                    case '1':
+                    case '2':
+                    case '3':
+                    case '4':
+                    case '5':
+                    case '6':
+                    case '7':
+                    case '8':
+                    case '9':
+                        switch (state) {
+                            case 1:
+                            case 2:
+                                state = 2;
+                                hTimezone.append(c);
+                                break;
+                            case 3:
+                                mTimezone.append(c);
+                                break;
+                            default:
+                                throw new ParseException(error, i);
+                        }
+                        break;
+                    default:
+                        throw new ParseException(error, i);
+                }
+            }
+
+            int h = 0;
+            int m = 0;
             try {
-                if (offset.startsWith("+")) {
-                    offset = offset.substring(1);
-                }
-                return Long.parseLong(offset);
+                h = Integer.parseInt(hTimezone.toString());
+                if (mTimezone.length() > 0) {
+                    m = Integer.parseInt(mTimezone.toString());
+                }
             } catch (NumberFormatException nfe) {
+                // Invalid timezone
                 throw new ParseException(error, 0);
             }
-        } else {
-            return 0;
+
+            if (h > 12 || m > 59)
+                throw new ParseException(error, 0);
+            else
+                return new Timezone((h + m / 60.0) * (sgnTimezone == '-' ? -1 : 1));
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof Timezone)) return false;
+            Timezone timezone1 = (Timezone) o;
+            return Double.compare(timezone1.timezone, timezone) == 0;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(timezone);
+        }
+    }
+
+    static final class Offset {
+
+        static final Offset ZERO = new Offset(0);
+        private final long milliseconds;
+
+        private Offset(long milliseconds) {
+            this.milliseconds = milliseconds;
+        }
+
+        static Offset milliseconds(long milliseconds) {
+            return new Offset(milliseconds);
+        }
+
+        static Offset seconds(long seconds) {
+            return new Offset(1000 * seconds);
+        }
+
+        long getMilliseconds() {
+            return milliseconds;
+        }
+
+        long getSeconds() {
+            return milliseconds / 1000;
+        }
+
+        String formatOffset() {
+            return Long.toString(milliseconds / 1000);
+        }
+
+        static Offset parseOffset(String offset) throws ParseException {
+            String error = tr("Error while parsing offset.\nExpected format: {0}", "number");
+
+            if (!offset.isEmpty()) {
+                try {
+                    if (offset.startsWith("+")) {
+                        offset = offset.substring(1);
+                    }
+                    return Offset.seconds(Long.parseLong(offset));
+                } catch (NumberFormatException nfe) {
+                    throw new ParseException(error, 0);
+                }
+            } else {
+                return Offset.ZERO;
+            }
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof Offset)) return false;
+            Offset offset = (Offset) o;
+            return milliseconds == offset.milliseconds;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(milliseconds);
         }
     }
Index: /trunk/test/unit/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImagesTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImagesTest.java	(revision 9740)
+++ /trunk/test/unit/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImagesTest.java	(revision 9741)
@@ -73,30 +73,38 @@
         i0.setExifTime(DateUtils.fromString("2016:01:03 11:59:54")); // 4 sec before start of GPX
         i0.createTmp();
-        assertEquals(Pair.create(0.0, -4L), CorrelateGpxWithImages.autoGuess(Collections.singletonList(i0), gpx));
+        assertEquals(Pair.create(CorrelateGpxWithImages.Timezone.ZERO, CorrelateGpxWithImages.Offset.seconds(-4)),
+                CorrelateGpxWithImages.autoGuess(Collections.singletonList(i0), gpx));
     }
 
     @Test
     public void testFormatTimezone() throws Exception {
-        assertEquals("+1:00", CorrelateGpxWithImages.formatTimezone(1));
-        assertEquals("+6:30", CorrelateGpxWithImages.formatTimezone(6.5));
-        assertEquals("-6:30", CorrelateGpxWithImages.formatTimezone(-6.5));
-        assertEquals("+3:08", CorrelateGpxWithImages.formatTimezone(Math.PI));
-        assertEquals("+2:43", CorrelateGpxWithImages.formatTimezone(Math.E));
+        assertEquals("+1:00", new CorrelateGpxWithImages.Timezone(1).formatTimezone());
+        assertEquals("+6:30", new CorrelateGpxWithImages.Timezone(6.5).formatTimezone());
+        assertEquals("-6:30", new CorrelateGpxWithImages.Timezone(-6.5).formatTimezone());
+        assertEquals("+3:08", new CorrelateGpxWithImages.Timezone(Math.PI).formatTimezone());
+        assertEquals("+2:43", new CorrelateGpxWithImages.Timezone(Math.E).formatTimezone());
     }
 
     @Test
     public void testParseTimezone() throws ParseException {
-        assertEquals(1, CorrelateGpxWithImages.parseTimezone("+01:00"), 1e-3);
-        assertEquals(1, CorrelateGpxWithImages.parseTimezone("+1:00"), 1e-3);
-        assertEquals(1.5, CorrelateGpxWithImages.parseTimezone("+01:30"), 1e-3);
-        assertEquals(11.5, CorrelateGpxWithImages.parseTimezone("+11:30"), 1e-3);
+        assertEquals(1, CorrelateGpxWithImages.Timezone.parseTimezone("+01:00").getHours(), 1e-3);
+        assertEquals(1, CorrelateGpxWithImages.Timezone.parseTimezone("+1:00").getHours(), 1e-3);
+        assertEquals(1.5, CorrelateGpxWithImages.Timezone.parseTimezone("+01:30").getHours(), 1e-3);
+        assertEquals(11.5, CorrelateGpxWithImages.Timezone.parseTimezone("+11:30").getHours(), 1e-3);
+    }
+
+    @Test
+    public void testFormatOffest() throws ParseException {
+        assertEquals("0", CorrelateGpxWithImages.Offset.seconds(0).formatOffset());
+        assertEquals("123", CorrelateGpxWithImages.Offset.seconds(123).formatOffset());
+        assertEquals("-4242", CorrelateGpxWithImages.Offset.seconds(-4242).formatOffset());
     }
 
     @Test
     public void testParseOffest() throws ParseException {
-        assertEquals(0, CorrelateGpxWithImages.parseOffset("0"));
-        assertEquals(4242L, CorrelateGpxWithImages.parseOffset("4242"));
-        assertEquals(-4242L, CorrelateGpxWithImages.parseOffset("-4242"));
-        assertEquals(0L, CorrelateGpxWithImages.parseOffset("-0"));
+        assertEquals(0, CorrelateGpxWithImages.Offset.parseOffset("0").getSeconds());
+        assertEquals(4242L, CorrelateGpxWithImages.Offset.parseOffset("4242").getSeconds());
+        assertEquals(-4242L, CorrelateGpxWithImages.Offset.parseOffset("-4242").getSeconds());
+        assertEquals(0L, CorrelateGpxWithImages.Offset.parseOffset("-0").getSeconds());
     }
 }
