001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.layer.geoimage; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.text.ParseException; 007import java.util.Locale; 008import java.util.Objects; 009import java.util.concurrent.TimeUnit; 010 011import org.openstreetmap.josm.tools.JosmDecimalFormatSymbolsProvider; 012import org.openstreetmap.josm.tools.Pair; 013 014/** 015 * Time offset of GPX correlation. 016 * @since 11914 (extracted from {@link CorrelateGpxWithImages}) 017 */ 018public final class Offset { 019 020 static final Offset ZERO = new Offset(0); 021 private final long milliseconds; 022 023 private Offset(long milliseconds) { 024 this.milliseconds = milliseconds; 025 } 026 027 static Offset milliseconds(long milliseconds) { 028 return new Offset(milliseconds); 029 } 030 031 static Offset seconds(long seconds) { 032 return new Offset(1000 * seconds); 033 } 034 035 long getMilliseconds() { 036 return milliseconds; 037 } 038 039 long getSeconds() { 040 return milliseconds / 1000; 041 } 042 043 String formatOffset() { 044 if (milliseconds % 1000 == 0) { 045 return Long.toString(milliseconds / 1000); 046 } else if (milliseconds % 100 == 0) { 047 return String.format(Locale.ENGLISH, "%.1f", milliseconds / 1000.); 048 } else { 049 return String.format(Locale.ENGLISH, "%.3f", milliseconds / 1000.); 050 } 051 } 052 053 static Offset parseOffset(String offset) throws ParseException { 054 String error = tr("Error while parsing offset.\nExpected format: {0}", "number"); 055 056 if (!offset.isEmpty()) { 057 try { 058 if (offset.startsWith("+")) { 059 offset = offset.substring(1); 060 } 061 return Offset.milliseconds(Math.round(JosmDecimalFormatSymbolsProvider.parseDouble(offset) * 1000)); 062 } catch (NumberFormatException nfe) { 063 throw (ParseException) new ParseException(error, 0).initCause(nfe); 064 } 065 } else { 066 return Offset.ZERO; 067 } 068 } 069 070 int getDayOffset() { 071 // Find day difference 072 return (int) Math.round(((double) getMilliseconds()) / TimeUnit.DAYS.toMillis(1)); 073 } 074 075 Offset withoutDayOffset() { 076 return milliseconds(getMilliseconds() - TimeUnit.DAYS.toMillis(getDayOffset())); 077 } 078 079 Pair<Timezone, Offset> splitOutTimezone() { 080 // In hours 081 final double tz = ((double) withoutDayOffset().getSeconds()) / TimeUnit.HOURS.toSeconds(1); 082 083 // Due to imprecise clocks we might get a "+3:28" timezone, which should obviously be 3:30 with 084 // -2 minutes offset. This determines the real timezone and finds offset. 085 final double timezone = (double) Math.round(tz * 2) / 2; // hours, rounded to one decimal place 086 final long delta = Math.round(getMilliseconds() - timezone * TimeUnit.HOURS.toMillis(1)); 087 return Pair.create(new Timezone(timezone), Offset.milliseconds(delta)); 088 } 089 090 @Override 091 public boolean equals(Object o) { 092 if (this == o) return true; 093 if (!(o instanceof Offset)) return false; 094 Offset offset = (Offset) o; 095 return milliseconds == offset.milliseconds; 096 } 097 098 @Override 099 public int hashCode() { 100 return Objects.hash(milliseconds); 101 } 102}